Transcriptome summary for patient sample test_sample_WTS.

##### We attempt to structure the script in the following way:
# 1. Defining functions
# 2. Loading libraries
# 3. Loading sample data and reference datasets
# Then... code chunks involving data processing
# Then... code chunks calling the processed data to produce tables / plots / data summary
# Finish with Session info in Addendum section

##### The processed data is stored in "ref_dataset.list" list variable with elements holding the following data:
# 1. ref_dataset.list[[dataset]][["combined_data"]] = combined read count data (reference datasets + sample data) ("combineDatasets" function output in the "load_ref_data chunk")
# 2. ref_dataset.list[[dataset]][["sample_annot"]] = combined data samples annotation ("combineDatasets" function output in the "load_ref_data chunk")
# 3. ref_dataset.list[[dataset]][["clinical_info"]] = clinical information (survival and treatment info)
# 4. ref_dataset.list[[dataset]][["combined_data_processed"]] = transformed, filtered and normalised data (see "data_transformation" and "data_normalisation" chunks)
# 5. ref_dataset.list[[dataset]][["batch_effect_corrected"]] = transformed, filtered, normalised and batch effect corrected data (see "batch_effect_correction" chunk)
# 6. ref_dataset.list[[dataset]][["pca_combined_data_processed"]] = PCA results for combined data
# 7. ref_dataset.list[[dataset]][["pca_batch_effect_corrected"]] = PCA results for batch-effect corrected data
# 8. ref_dataset.list[[dataset]][["rle_combined_data_processed"]] = RLE plot for combined data
# 9. ref_dataset.list[[dataset]][["rle_batch_effect_corrected"]] = RLE plot for batch-effect corrected data
# 10. ref_dataset.list[[dataset]][["data_to_report"]] = Fully combined and processed data to be used for reporting
# 11. ref_dataset.list[[dataset]][["gene_annot_all"]] = gene annotation for combined read count data, containing all input genes. The annotation includes "SYMBOL", "GENEBIOTYPE", "ENSEMBL", "SEQNAME", "GENESEQSTART", "GENESEQEND". "ENSEMBL" is used for rownames
# 12. ref_dataset.list[[dataset]][["gene_annot"]] = gene annotation for transformed, filtered and normalised data. The annotation includes "SYMBOL", "GENEBIOTYPE", "ENSEMBL", "SEQNAME", "GENESEQSTART", "GENESEQEND". "SYMBOL" is used for rownames
# 13. ref_dataset.list[[dataset]][["expr_mut_cn_data_all"]] = combined expression, mutation and copy-number data
# 14. ref_dataset.list[[dataset]][["expr_mut_cn_data"]] = combined expression, mutation and copy-number data limited to cancer genes that meet user-deinfed CN values threshold

##### Genes of interest are stored in "ref_genes.list" list variable with elements holding the following gene sets:
# 1. ref_genes.list[["genes_cancer"]] = list of cancer genes derived from UMCCR Cancer Gene list (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv) and OncoKB portal (http://oncokb.org/#/cancerGenes) 
# 2. ref_genes.list[["genes_oncokb"]] = list of cancer genes derived from OncoKB portal (http://oncokb.org/#/cancerGenes) alone (although genes present on the UMCCR panel are also flagged)
# 3. ref_genes.list[["genes_immune"]] = list of immune reponse markers provided in the "An Immunogram for the Cancer-Immunity Cycle" paper by Karasaki at al (2017) (https://www.ncbi.nlm.nih.gov/pubmed/28088513) and OmniSeq report (https://www.omniseq.com/) 
# 4. ref_genes.list[["genes_hrd"]] = list of hrd (homologous recombination deficiency) genes
# 5. ref_genes.list[["pcgr"]] = list and PCGR annotation of mutated genes in given patient based on PCGR report
# 6. ref_genes.list[["purple"]] = list and PURPLE annotation of copy-number (CN) altered genes in given patient based on PURPLE results
# 7. ref_genes.list[["manta"]] = list and MANTA annotation of structural variants (SVs) with affected genes in given patient based on MANTA results
# 8. ref_genes.list[["arriba"]] = list and ARRIBA annotation of gene fusion events detected in given patient based on ARRIBA results
# 9. ref_genes.list[["pizzly"]] = list and PIZZLY annotation of gene fusion events detected in given patient based on PIZZLY results
# 10. ref_genes.list[["summary"]] = summary of above-mentioned gene lists. These gene lists are also used for generating expression summary tables and plots in individual report sections
NOW <- Sys.time()

##### Time chunks during knitting
knitr::knit_hooks$set(timeit = function(before) {
  
  if (before) {
    print(paste("Start:", Sys.time()))
    NOW <<- Sys.time()
  } else {
    print(paste("Stop:", Sys.time()))
    print(Sys.time() - NOW)
  }
})

knitr::opts_chunk$set(timeit = TRUE)
##### Define functions
##### Create 'not in' operator
"%!in%" <- function(x,table) match(x,table, nomatch = 0) == 0

##### Prepare object to write into a file
prepare2write <- function (x) {
  
  x2write <- cbind(rownames(x), x)
  colnames(x2write) <- c("",colnames(x))
  
  ##### Clean the space and return output
  rm(x)
  return(x2write)
}

##### Combine sample expression profile with reference datasets. This function outputs a vector with first element containing the merged data and second element containing merged targets info
combineDatasets <- function(sample_name, sample_counts, ref_data, report_dir, dataset) {
  
  ##### Extract info about target file for the external reference dataset
  target.ext <- read.table(ref_data[["ext_ref"]][2], sep="\t", as.is=TRUE, header=TRUE)
  target.ext <- cbind(target.ext, rep(ref_data[["ext_ref"]][3], nrow(target.ext)))
  colnames(target.ext)[ncol(target.ext)] <- "Dataset"
  
  ##### Add prexit to sample names
  rownames(target.ext) <- paste(target.ext[,"Dataset"], target.ext[,"Sample_name"], sep = ".")
  target.ext <- target.ext[, -1]
  
  ##### Extract info about target file for the internal reference dataset
  target.int <- read.table(ref_data[["int_ref"]][2], sep="\t", as.is=TRUE, header=TRUE)
  target.int <- cbind(target.int, rep(ref_data[["int_ref"]][3], nrow(target.int)))
  colnames(target.int)[ncol(target.int)] <- "Dataset"
      
  ##### Add prexit to sample names
  rownames(target.int) <- paste(target.int[,"Dataset"], target.int[,"Sample_name"], sep = ".")
  target.int <- target.int[, -1]
      
  target.comb <- rbind(target.ext, target.int)
  
  ##### Add sample info
  target.sample <- data.frame(sample_name, sample_name)
  names(target.sample) <- names(target.comb)
  rownames(target.sample) <- sample_name
  target.comb <- rbind( target.comb, target.sample )
  
  ##### Make syntactically valid names
  rownames(target.comb) <- make.names(rownames(target.comb))
  
  ##### Read sample read count file and combine it with reference datasets
  datasets.comb <- sample_counts
  names(datasets.comb) <- c("", sample_name)
      
  ##### list genes present in the sample read count file
  gene_list <- as.vector(datasets.comb[,1])
      
  ##### Loop through the expression data from different datasets and merge them into one matrix
  for ( i in 1:length(ref_data) ) {
    
    dataset.counts <- as.data.frame( read.table(gzfile(ref_data[[i]][1]), header=TRUE, sep="\t", row.names=NULL) )
    
    ##### Add prexit to sample names
    colnames(dataset.counts) <- paste(unique(target.comb[,"Dataset"])[i], colnames(dataset.counts), sep = ".")
    
    ##### List genes present in individal files
    gene_list <- c( gene_list, as.vector(dataset.counts[,1]) )
    
    ##### Merge the expression datasets and make sure that the genes order is the same
    datasets.comb <- merge( datasets.comb, dataset.counts, by=1, all = FALSE, sort= TRUE)
  }
  
  ##### Use gene IDs as rownames
  rownames(datasets.comb) <- datasets.comb[,1]
  datasets.comb <- datasets.comb[, -1]
  
  ##### Make syntactically valid names
  colnames(datasets.comb) <- make.names(colnames(datasets.comb))
  
  ##### Make sure that the target file contains info only about samples present in the data matrix
  target.comb <- target.comb[ rownames(target.comb) %in% colnames(datasets.comb),  ]
  
  ##### Make sure that the samples order in the data matrix is the same as in the target file 
  datasets.comb <- datasets.comb[ , rownames(target.comb) ]
  
  ##### Identify genes that were not present across all per-sampel files and were ommited in the merged matrix
  gene_list <- unique(gene_list)
  gene_list.missing <- gene_list[ gene_list %!in% rownames(datasets.comb) ]
  
  ##### Write list of missing genes into a file
  if ( length(gene_list.missing) > 0 ) {
    write.table(prepare2write(gene_list.missing), file = paste0(report_dir, "/", sample_name, ".RNAseq_report.missing_genes.txt"), sep="\t", quote=FALSE, row.names=TRUE, append = FALSE )
  }
  
  ##### Clean the space and return output
  rm(sample_name, sample_counts, ref_data, target.ext, target.int, target.sample, dataset.counts, gene_list, gene_list.missing)
  return( list(datasets.comb, target.comb) )
}

##### Assign colours to different elements
getColours <- function(elements) {
  
  ##### Predefined selection of colours for elements
  if ( length(unique(elements)) == 3 ) {
    elements.colours <- c("powderblue", "red", "gray50")
  } else if ( length(unique(elements)) == 4 ) {
    elements.colours <- c("powderblue", "forestgreen", "red", "gray50")
  } else {
    elements.colours <- rainbow(length(elements))
  }
  
  f.elements <- factor(elements, levels = unique(elements))
  vec.elements <- elements.colours[1:length(levels(f.elements))]
  elements.colour <- rep(0,length(f.elements))
  for (i in 1:length(f.elements))
    elements.colour[i] <- vec.elements[ f.elements[i]==levels(f.elements)]
  
  return( list(vec.elements, elements.colour) )
}

##### Calculate TPM from RPKM (from http://luisvalesilva.com/datasimple/rna-seq_units.html )
tpm_from_rpkm <- function(x) {
  rpkm.sum <- colSums(x)
  return(t(t(x) / (1e-06 * rpkm.sum)))
}

##### Function to generate a full-resolution pdf image before generating a small image in the chunk (from https://stackoverflow.com/questions/37834053/what-is-a-simple-way-to-thumbnail-some-plots-in-r-markdown-knitr )
allow_thumbnails <- function(x, options) {
  if (!is.null(options$thumb)) {
    filename <- sprintf("%s.full.pdf", strsplit(basename(x), "\\.")[[1]][1])
    absolute_path <- file.path(dirname(x), filename)

    ##### Generate the full resolution pdf
    pdf(absolute_path, width = options$thumb$width, height = options$thumb$height)
      eval(parse(text = options$code))
    dev.off()

    ##### Add an html link to the low resolution png
    options$fig.link = absolute_path
  }

  knitr:::hook_plot_md_base(x, options)
}

##### Perform PCA. This function outputs a list with dataframe and samples colouring info ready for plotting
pca <- function(data, targets, title = "", report_dir, suffix = "" ) {

  ##### Keep only genes with variance > 0 across all samples
  rsd <- apply(data,1,sd)
  data.subset <- data[rsd>0,]
  
  ##### Perform PCA
  data.subset_pca <- prcomp(t(data.subset), scale=FALSE)
  
  ##### Get variance importance for all principal components
  importance_pca <- summary(data.subset_pca)$importance[2,]
  importance_pca <- paste(round(100*importance_pca, 2), "%", sep="")
  names(importance_pca) <- names(summary(data.subset_pca)$importance[2,])
    
  ##### Prepare data frame
  data.subset_pca.df <- data.frame(targets$Target, targets$Dataset, data.subset_pca$x[,"PC1"], data.subset_pca$x[,"PC2"], data.subset_pca$x[,"PC3"])
  colnames(data.subset_pca.df) <- c("Target", "Dataset", "PC1", "PC2", "PC3")
  
  ##### Assigne colours to targets and datasets
  targets.colour <- getColours(targets$Target)
  datasets.colour <- getColours(targets$Dataset)
  
  ##### Create a list with dataframe and samples colouring info
  pca.list <- list(data.subset_pca.df, importance_pca, targets.colour, datasets.colour)
  names(pca.list) <- c("pca.df", "importance_pca", "targets", "datasets")
  
  ##### Change the datasets levels order
  data.subset_pca.df$Target <- factor(data.subset_pca.df$Target, levels = unique(data.subset_pca.df$Target))
  
  ##### Generate PCA 2-D plot
  pca_plot <- plot_ly(data.subset_pca.df, x = ~PC1, y = ~PC2, color = ~Target, text=paste(targets$Target, rownames(data.subset_pca.df), sep=": "), colors = targets.colour[[1]], type='scatter', mode = "markers", marker = list(size=10, opacity = 0.7), width = 800, height = 500) %>%
  layout(title = title, xaxis = list(title = paste( "PC1", " (",importance_pca["PC1"],")",sep="")), yaxis = list(title = paste( "PC2", " (",importance_pca["PC2"],")",sep="")), margin = list(l=50, r=50, b=50, t=30, pad=4), autosize = FALSE, showlegend = TRUE, legend = list(orientation = "v", y = 0.9))

  ##### Generate Scree-plot
  data.subset_scree.df <- data.frame(paste0("PC ", c(1:length(importance_pca))), as.numeric(gsub("%", "",importance_pca)))
colnames(data.subset_scree.df) <- c("PC", "Variances")

  ##### The default order will be alphabetized unless specified as below
  data.subset_scree.df$PC <- factor(data.subset_scree.df$PC, levels = data.subset_scree.df[["PC"]])
  
  scree_plot <- plot_ly(data.subset_scree.df, x = ~PC, y = ~Variances, type = 'bar', width = 800, height = 350) %>%
    layout(title = title, xaxis = list(title = ""), margin = list(l=50, r=50, b=100, t=30, pad=4), autosize = F)
  
  ##### Create directory for the plots
  PCAplotDir <- paste(report_dir, "InputDataPlots", sep = "/")
  if ( !file.exists(PCAplotDir) ) {
    dir.create(PCAplotDir, recursive=TRUE)
  }
  
  ##### Save interactive plot as html file
  saveWidgetFix(pca_plot, file = paste0(PCAplotDir, "/pca_plot", suffix, ".html"))
  saveWidgetFix(scree_plot, file = paste0(PCAplotDir, "/scree_plot", suffix, ".html"))
  
  return( list(pca.list, pca_plot, scree_plot) )
  
  ##### Clean the space
  rm(data, targets, rsd, data.subset, data.subset_pca, importance_pca, data.subset_pca.df, targets.colour, datasets.colour, pca.list, data.subset_scree.df, PlotsDir)
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Convert a vector of numbers into corresponding vector of their percentiles
perc.rank <- function(x) trunc(rank(x))*100/length(x)

##### Perform range standardization between 0 and 1 (for the cumulative sums)
standardization <- function(x) c(x-min(x))/(max(x)-min(x))

##### Calculating cumulative sum for while keeping the original data order
cumsum_ordered <- function(x) {
  
  ##### Perform range standardization between 0 and 1, otherwise the negative values are summed up
  standarised <- standardization(x)
  
  ##### Sort and cumsum values
  sorted_cumsum <- cumsum(sort(standarised))
  
  ##### Restore the original elements order
  ordered_cumsum <- sorted_cumsum[ names(standarised) ]
  
  ##### Perform range standardization between 0 and 1, otherwise the negative values are summed up
  standarised_cumsum <- standardization(ordered_cumsum)
  
  ##### Clean the space and return output
  rm(x, standarised, sorted_cumsum, ordered_cumsum)
  return( standarised_cumsum )
}

##### Check for nearest value in a vector
nearest_position <- function(vector, x) {
  
  y <- which.min(abs(vector - x))
  
  ##### Clean the space and return output
  rm(vector, x)
  return( y )
}

##### Calculate gene-wise median, sd, quantiles and cumulative franctions for expression data
exprGroupsStats_geneWise <- function(data, targets) {
  
  ##### Perform Z-score transformation of the expression values
  data.z <- t(apply(data, 1, scale, scale = TRUE))
  colnames(data.z) <- colnames(data)
  
  ##### Remove rows with potential NA's, which is due to SD = 0 across all samples
  data.z <- data.z[rowSums(!is.na(data.z)) > 0, , drop = FALSE]
  data <- data[ rownames(data) %in% rownames(data.z), , drop = FALSE]
  
  ##### Perform the gene-wise calculations across all groups
  ##### Convert a expression values into corresponding percentiles
  data.q <- t(apply(data, 1, perc.rank))
 
  ##### Calculate cumulative sums and perform range standardization between 0 and 1
  data.cum <- t(apply(data, 1, cumsum_ordered))
 
  ##### Create lists with stats for each group and gene
  targets.list <- unique(targets$Target)
  group_stats.list <- vector("list", length(targets.list))
  names(group_stats.list) <- targets.list
  
  #### For each group...
  for ( group in targets.list ) {
    
    ##### For groups with > 1 sample get the median values for each gene
    if ( sum(c(targets$Target %in% group), na.rm = TRUE) > 1 && nrow(data) > 1 )  {
      
      ##### Extract the median expression values
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], rowMedians(data[ , colnames(data)[ targets$Target %in% group ] ]))

      ##### Extract the expression sd values
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], rowSds(data[ , colnames(data)[ targets$Target %in% group ] ]))
      
      ##### Extract the median Z-scores
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], rowMedians(data.z[ , colnames(data)[ targets$Target %in% group ] ]))

      ##### Extract the median percentiles
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], rowMedians(data.q[ , colnames(data)[ targets$Target %in% group ] ]))
      
      ##### Extract the cumulative fraction corresponding to the median Z-score
      ##### First, need to get the position of the Z-score nearest to the median Z-score, and then extract the cumulative value at this position
      data.z.median_pos <- apply(data.z, 1, nearest_position, median(data.z[ , colnames(data)[ targets$Target %in% group, drop = FALSE ] ]))
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], data.cum[ data.z.median_pos ] )
      
      group_stats.list[[group]] <- as.data.frame(group_stats.list[[group]])
      names( group_stats.list[[group]] ) <- c("median", "sd", "z", "quantile", "cum")
      rownames( group_stats.list[[group]] ) <- rownames(data)
      
    } else if ( sum(c(targets$Target %in% group), na.rm = TRUE) > 1 && nrow(data) == 1 ) {
      
      ##### Extract the median expression values
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], median(data[ , colnames(data)[ targets$Target %in% group, drop = FALSE ] ]))

      ##### Extract the expression sd values
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], sd(data[ , colnames(data)[ targets$Target %in% group, drop = FALSE ] ]))
      
      ##### Extract the median Z-scores
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], median(data.z[ , colnames(data)[ targets$Target %in% group, drop = FALSE ] ]))

      ##### Extract the median percentiles
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], median(data.q[ , colnames(data)[ targets$Target %in% group, drop = FALSE ] ]))
      
      ##### Extract the cumulative fraction corresponding to the median Z-score
      ##### First, need to get the position of the Z-score nearest to the median Z-score, and then extract the cumulative value at this position
      data.z.median_pos <- nearest_position( data.z, median(data.z[ , colnames(data)[ targets$Target %in% group, drop = FALSE ] ]))
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], data.cum[ data.z.median_pos ] ) 

      group_stats.list[[group]] <- as.data.frame(group_stats.list[[group]])
      names( group_stats.list[[group]] ) <- c("median", "sd", "z", "quantile", "cum")
      rownames( group_stats.list[[group]] ) <- rownames(data)
      
    } else {

      ##### Extract the median expression values
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], data[ , colnames(data)[ targets$Target %in% group ] ])

      ##### Extract the expression sd values
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], rep( NA, nrow(data)))
      
      ##### Extract the median Z-scores
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], data.z[ , colnames(data)[ targets$Target %in% group ] ])

      ##### Extract the median percentiles
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], data.q[ , colnames(data)[ targets$Target %in% group ] ])
      
      ##### Extract the median cumulative fraction
      group_stats.list[[group]] <- cbind(group_stats.list[[group]], data.cum[ , colnames(data)[ targets$Target %in% group ] ])
      
      group_stats.list[[group]] <- as.data.frame(group_stats.list[[group]])
      names( group_stats.list[[group]] ) <- c("median", "sd", "z", "quantile","cum")
      rownames( group_stats.list[[group]] ) <- rownames(data)
     }
  }
  
  ##### Finally, extract cumulative values for each gene within individual groups
  gene_stats.list <- vector("list", length(targets.list))
  names(gene_stats.list) <- targets.list
  
  #### For each group...
  for ( group in targets.list ) {
    
    ##### Extract per-gene expression values
    gene_stats.list[[group]]$median <- data[ , colnames(data)[ targets$Target %in% group ], drop = FALSE ]
    
    ##### Extract per-gene z-score values
    gene_stats.list[[group]]$z <- data.z[ , colnames(data.z)[ targets$Target %in% group ], drop = FALSE ]
    
    ##### Extract per-gene percentile values
    gene_stats.list[[group]]$q <- data.q[ , colnames(data.q)[ targets$Target %in% group ], drop = FALSE ]
    
    ##### Extract per-gene cumulative values
    gene_stats.list[[group]]$cum <- data.cum[ , colnames(data.cum)[ targets$Target %in% group ], drop = FALSE ]
  }
  
  ##### Clean the space and return output
  rm(data, targets, data.z, data.q, data.cum, targets.list, data.z.median_pos)
  return( list( group_stats.list, gene_stats.list) )
}

##### Calculate group-wise median, sd, quantiles and cumulative franctions for expression data from specific sample group
exprGroupStats_groupWise <- function(data, targets, target) {
  
  ##### Subset data for defined biological group
  data.group <- data[, targets$Target %in% target ]
  
  ##### For groups with > 1 sample get the median and standard deviation for each gene
  if ( !is.null(ncol(data.group)) )  {
    
    data.group.median <- rowMedians(data.group)
    names(data.group.median) <- rownames(data.group)
    data.group.median <- sort(data.group.median)
    data.group.sd <- rowSds(data.group)
    
  } else {
    data.group.median <- sort(data.group)
    data.group.sd <- rep( NA, length(data.group))
  }
  
  ##### Make sure the median and sd vectors have the same gene order
  names(data.group.sd) <- rownames(data.group)
  data.group.sd <- data.group.sd[names(data.group.median)]

  ##### Convert a expression values into corresponding percentiles
  data.group.q <- perc.rank(data.group.median)
  
  ##### Perform range standardization between 0 and 1 (for the cumulative sums), otherwise the negative values are summed up
  data.group.s <- sort(standardization(data.group.median))
  
  ##### Calculate cumulative sums and perform range standardization between 0 and 1 
  data.group.cum <- standardization(cumsum(data.group.s))
  
  ##### Perform Z-score transformation of the median expression values
  data.group.z <- scale(data.group.median, scale = FALSE)
  
  ##### Organise the data into data frame
  data.group.df <- as.data.frame(cbind( data.group.median, data.group.sd, data.group.z, data.group.q, data.group.cum))
  names(data.group.df) <- c("median", "sd", "z", "quantile", "cum")
  
  ##### Clean the space and return output
  rm(data, targets, target, data.group, data.group.median, data.group.sd, data.group.q, data.group.s, data.group.cum, data.group.z)
  return( data.group.df )
}

##### Generate cumulative distribution function (CDF) plot for selected gene. If option "addBoxPlot" = TRUE, then generate additional boxplot below to show the data variance for selected gene in individual groups
cdfPlot <- function(gene, data, targets, sampleName, int_cancer, ext_cancer, comp_cancer, add_cancer = NULL, addBoxPlot = FALSE, scaling = "gene-wise", report_dir) {
  
  ##### Remove the internal reference cohort data if the patient samples origins from other tissue. Of note, the internal reference cohort was only used to process the in-house data (including the investigated patient sample) and to correct batch-effects
  if ( comp_cancer != int_cancer ) {
    targets <- targets[ targets$Target %!in% int_cancer, ]
    data <- data[ ,rownames(targets) ]
  }
  
  ##### Initiate lists with stats for each group
  targets.list <- unique(targets$Target)
  group.z <- vector("list", length(targets.list))
  names(group.z) <- targets.list
  
  ##### .... and for selected gene
  group.z.gene <- vector("list", length(targets.list))
  names(group.z.gene) <- targets.list

  ##### Get expression-related stats for each group
  ##### ... from gene-wise approach 
  if ( scaling == "gene-wise" ) {

    ##### Get stats for each group
    gene.data <- data[ gene, , drop = FALSE]
    group.z.gene <- exprGroupsStats_geneWise(gene.data, targets)[[1]]
    
    ##### ... and for each sample in individual groups
    gene.stats <- exprGroupsStats_geneWise(gene.data, targets)[[2]]

    for ( group in targets.list ) {
        group.z[[ group]] <- cbind(t(gene.stats[[ group]]$median), t(gene.stats[[ group]]$z), t(gene.stats[[ group]]$q), t(gene.stats[[ group]]$cum) )
        group.z[[ group]] <- as.data.frame(group.z[[ group]])
        colnames(group.z[[ group]]) <- c("median", "z", "quantile", "cum")
    }
    
    group.z[[ sampleName ]] <- do.call("rbind", group.z)
    
  ##### ... or from group-wise approach
  } else {
    group.z[[ sampleName ]] <- exprGroupStats_groupWise(data, targets, sampleName)
    group.z[[ ext_cancer ]] <- exprGroupStats_groupWise(data, targets, ext_cancer)
    
    ##### Extract expression for selected genes
    group.z.gene[[ sampleName ]] <- group.z[[ sampleName ]][ rownames(group.z[[ sampleName ]]) %in% gene, ]
    group.z.gene[[ ext_cancer ]] <- group.z[[ ext_cancer ]][ rownames(group.z[[ ext_cancer ]]) %in% gene, ]
    
    ##### Add info for internal cohort
    if ( comp_cancer == int_cancer ) {
      group.z[[ int_cancer ]] <- exprGroupStats_groupWise(data, targets, int_cancer)
      group.z.gene[[ int_cancer ]] <- group.z[[ int_cancer ]][ rownames(group.z[[ int_cancer ]]) %in% gene, ]
    }
    
    ##### Add info for additional cancer type is specified
    if ( !is.null(add_cancer) ) {
      group.z[[ add_cancer ]] <- exprGroupStats_groupWise(data, targets, add_cancer)
      group.z.gene[[ add_cancer ]] <- group.z[[ add_cancer ]][ rownames(group.z[[ add_cancer ]]) %in% gene, ]
    }
  }
  
  ##### Generate box-plot for selected gene
  if ( addBoxPlot ) {
    ##### Perform Z-score transformation of the median expression values
    if ( scaling == "gene-wise" ) {
      
      data.z <- t(scale(t(data)))
    } else {
      data.z <- scale(data, scale = FALSE)
    }
    
    targets$Target[ targets$Target==sampleName ] <- "Patient"
    gene.expr.df <- data.frame(targets$Target, data.z[gene, ])
    colnames(gene.expr.df) <- c("Group", "Expression")
    
    ##### Reorder groups
    if ( !is.null(add_cancer) ) {
      gene.expr.df$Group <- factor(gene.expr.df$Group, levels=c( add_cancer, ext_cancer, int_cancer, "Patient"))
      group.colours <- c("forestgreen", "cornflowerblue", "red", "black")
    } else {
      gene.expr.df$Group <- factor(gene.expr.df$Group, levels=c(ext_cancer, int_cancer, "Patient"))
      group.colours <- c("cornflowerblue", "red", "black")
    }
    
    p2 <- plot_ly(gene.expr.df, x= ~Expression, color = ~Group, type = 'box', jitter = 0.3, pointpos = 0, boxpoints = 'all', colors = group.colours, opacity = 0.5, orientation = 'h', width = 800, height = 400, showlegend=FALSE)
  }
  
  ##### Generate interactive CDF plot with plotly
  ##### Include the internal reference cohort in the plot
  if ( comp_cancer == int_cancer ) {
    p1 <- plot_ly(group.z[[ sampleName ]], x = ~z, color = I("black"), width = 700, height = 200) %>%
    
      ##### Add sample data
      add_markers(y = group.z.gene[[ sampleName ]]$quantile, x = group.z.gene[[ sampleName ]]$z,
                  text = rownames(group.z.gene[[ sampleName ]] ),
                  name = "Patient",
                  marker = list(size = 12, color = "black"),
                  showlegend = TRUE) %>%
    
      add_lines(y = group.z[[ sampleName ]]$quantile, x = group.z[[ sampleName ]]$z, 
                line = list(color = "grey"),
                text = rownames( group.z[[ sampleName ]] ),
                name = "Patient", showlegend = FALSE) %>%
        
      ##### Add int_cancer data
      add_markers(y = group.z.gene[[ int_cancer ]]$quantile, x =  group.z.gene[[ int_cancer ]]$z,
                  text = rownames( group.z.gene[[ int_cancer ]]),
                  name = int_cancer,
                  marker = list(size = 12, opacity = 0.5, color = "red"),
                  showlegend = TRUE) %>%
    
      add_lines(y = group.z[[ int_cancer ]]$quantile, x = group.z[[ int_cancer ]]$z, opacity = 0.5,
                line = list(color = "red", dash = "dash"),
                text = rownames( group.z[[ int_cancer ]] ),
                name = int_cancer, showlegend = FALSE) %>%
          
      ##### Add ext_cancer data
      add_markers(y = group.z.gene[[ ext_cancer ]]$quantile, x =  group.z.gene[[ ext_cancer ]]$z,
                  text = rownames( group.z.gene[[ ext_cancer ]] ),
                  name = ext_cancer,
                  marker = list(size = 12, opacity = 0.5, color = "cornflowerblue"),
                  showlegend = TRUE) %>%
    
      add_lines(y = group.z[[ ext_cancer ]]$quantile, x = group.z[[ ext_cancer ]]$z, opacity = 0.5,
                line = list(color = "cornflowerblue", dash = "dash"),
                text = rownames( group.z[[ ext_cancer ]] ),
                name = ext_cancer, showlegend = FALSE) %>%
      
      ##### Add quantile lines
      add_lines(y = seq(0,100,10), x = rep(quantile(group.z[[ sampleName ]]$z)[2], 11), opacity = 0.5,
                line = list(color = "gray", dash = "dash"),
                name = "Q1", showlegend = FALSE) %>%
      
      add_lines(y = seq(0,100,10), x = rep(quantile(group.z[[ sampleName ]]$z)[3], 11), opacity = 0.5,
                line = list(color = "gray", dash = "dash"),
                name = "Q2", showlegend = FALSE) %>%
      
      add_lines(y = seq(0,100,10), x = rep(quantile(group.z[[ sampleName ]]$z)[4], 11), opacity = 0.5,
                line = list(color = "gray", dash = "dash"),
                name = "Q3", showlegend = FALSE) %>% 
      
          layout(title = gene, xaxis = list(title = "mRNA expression (Z-score)", zeroline = FALSE, range = c(min(group.z[[ sampleName ]]$z)-1.5, max(group.z[[ sampleName ]]$z)+1.5)),
             yaxis = list(title = "Percentile"),
             legend = list(orientation = 'v', x = 0.02, y = 1, bgcolor = "white")
      )
  
  ##### Skip the internal reference cohort in the plot
  } else {
    p1 <- plot_ly(group.z[[ sampleName ]], x = ~z, color = I("black"), width = 700, height = 200) %>%
  
    ##### Add sample data
    add_markers(y = group.z.gene[[ sampleName ]]$quantile, x = group.z.gene[[ sampleName ]]$z,
                text = rownames(group.z.gene[[ sampleName ]] ),
                name = "Patient",
                marker = list(size = 12, color = "black"),
                showlegend = TRUE) %>%
  
    add_lines(y = group.z[[ sampleName ]]$quantile, x = group.z[[ sampleName ]]$z, 
              line = list(color = "grey"),
              text = rownames( group.z[[ sampleName ]] ),
              name = "Patient", showlegend = FALSE) %>%
        
    ##### Add ext_cancer data
    add_markers(y = group.z.gene[[ ext_cancer ]]$quantile, x =  group.z.gene[[ ext_cancer ]]$z,
                text = rownames( group.z.gene[[ ext_cancer ]] ),
                name = ext_cancer,
                marker = list(size = 12, opacity = 0.5, color = "cornflowerblue"),
                showlegend = TRUE) %>%
  
    add_lines(y = group.z[[ ext_cancer ]]$quantile, x = group.z[[ ext_cancer ]]$z, opacity = 0.5,
              line = list(color = "cornflowerblue", dash = "dash"),
              text = rownames( group.z[[ ext_cancer ]] ),
              name = ext_cancer, showlegend = FALSE) %>%
    
    ##### Add quantile lines
    add_lines(y = seq(0,1,0.1), x = rep(quantile(group.z[[ sampleName ]]$z)[2], 11), opacity = 0.5,
              line = list(color = "gray", dash = "dash"),
              name = "Q1", showlegend = FALSE) %>%
    
    add_lines(y = seq(0,1,0.1), x = rep(quantile(group.z[[ sampleName ]]$z)[3], 11), opacity = 0.5,
              line = list(color = "gray", dash = "dash"),
              name = "Q2", showlegend = FALSE) %>%
    
    add_lines(y = seq(0,1,0.1), x = rep(quantile(group.z[[ sampleName ]]$z)[4], 11), opacity = 0.5,
              line = list(color = "gray", dash = "dash"),
              name = "Q3", showlegend = FALSE) %>% 
    
        layout(title = gene, xaxis = list(title = "mRNA expression (Z-score)", zeroline = FALSE, range = c(min(group.z[[ sampleName ]]$z)-1.5, max(group.z[[ sampleName ]]$z)+1.5)),
           yaxis = list(title = "Percentile"),
           legend = list(orientation = 'v', x = 0.02, y = 1, bgcolor = "white")
    )
  }
  
  ##### Combine CDF plot with boxplot if this option is selected
  if ( addBoxPlot ) {
    p1_2 <- subplot(p1, p2, nrows = 2, shareX = TRUE, shareY = FALSE, titleY = TRUE, heights = c(0.7, 0.3)) %>%
  layout(xaxis = list(title = "mRNA expression (Z-score)", zeroline = FALSE, range = c(min(group.z[[ sampleName ]]$z)-1.5, max(group.z[[ sampleName ]]$z)+1.5)),
          yaxis = list(title = "Percentile"),
          legend = list(orientation = 'v', x = 0.02, y = 1, bgcolor = "white"),
          yaxis2 = list( title =""), xaxis2 = list(title = paste0(gene, " mRNA expression (Z-score)")), margin = list(l=50, r=50, b=50, t=50, pad=4), autosize = FALSE,
         showlegend=TRUE, showlegend2=FALSE)
    
    return( p1_2 )
    
  } else {
    return( p1 )
  }
  ##### Clean the space
  rm(gene, targets, data, sampleName, targets.list, group.z, group.z.gene, gene.data, gene.stats, data.z, gene.expr.df, group.colours)
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Convert density to counts
density2freq <- function(density) {
  freq = length(density)/sum(density) * density
  return(freq)
}

##### Generate density and expression distribution plots for selected gene, highlighting samples of interest
densityPlot <- function(gene, data, main_title, x_title, sampleName, distributions = NULL, scaling = "gene-wise") {
  
  if ( scaling == "gene-wise" ) {
    data.z <- t(scale(t(data)))
  } else {
    data.z <- scale(data, scale = FALSE)
  }
  
  ##### Used data for user-defined genes
  data.z <- data.z[ gene, ,drop=FALSE]

  ##### Create data frame and fill it with expression and density values for each sample for selected gene
  data.df <- data.frame(gene = "Observed distribution", sample = colnames(data.z)[order(data.z)], expr = sort(data.z), dens = density2freq(density(data.z, n=ncol(data.z))$y))
  
  ##### Generate values to generate various distributions
  if ( !is.null(distributions) ) {
    
    ##### Use the density values obtained from the expression values
    expr.sorted <- sort(data.z)
    
    ##### Get min and max values based on the expression data
    data.x <- seq(min(expr.sorted), max(expr.sorted), length.out = ncol(data.z))
    
    ##### Create empty data frame
    data.df.dist <- data.frame(matrix(ncol = 4, nrow = 0))
    colnames(data.df.dist) <- c("gene", "sample", "expr", "dens")
    
    ##### Generate y-values to mirror distributions of interest
    ##### Generate y-values for normal distribution. Useful resource https://stats.idre.ucla.edu/r/modules/probabilities-and-distributions/
    if ( "normal" %in% tolower(distributions) ) {
      data.y <- dnorm(data.x, mean = mean(data.x), sd = (max(data.x)-mean(data.x))/5)
      data.df.dist <- rbind(data.df.dist, data.frame(gene="Normal distribution", sample = colnames(data.z)[order(data.z)], expr=data.x, dens=density2freq(data.y)))
    } 
    
    ##### Generate x- and y-values for binomial distribution. Useful link https://stat.ethz.ch/R-manual/R-devel/library/stats/html/Binomial.html
    if ( "binomial" %in% tolower(distributions) ) {
      data.x <- 1:ncol(data.z)
      data.y <- dbinom(data.x, ncol(data.z), 0.25)
      data.x <- rescale(data.x, to = c(min(expr.sorted), max(expr.sorted)))
      data.df.dist <- rbind(data.df.dist, data.frame(gene="Binomial distribution (p=0.25)", sample = colnames(data.z)[order(data.z)], expr=data.x, dens=density2freq(data.y)))
      
      data.x <- 1:ncol(data.z)
      data.y <- dbinom(data.x, ncol(data.z), 0.75)
      data.x <- rescale(data.x, to = c(min(expr.sorted), max(expr.sorted)))
      data.df.dist <- rbind(data.df.dist, data.frame(gene="Binomial distribution (p=0.75)", sample = colnames(data.z)[order(data.z)], expr=data.x, dens=density2freq(data.y)))
    }
    
    ##### Draw n/2 samples from a normal distributions with one median and another n/2 samples from a second normal distribution with a different median. Useful link                  https://stats.stackexchange.com/questions/355344/simulating-a-bimodal-distribution-in-the-range-of-15-in-r
    if ( "bimodal" %in% tolower(distributions) ){
      data.x1 <- seq(min(expr.sorted), median(expr.sorted), length.out = ncol(data.z)/2)
      data.x2 <- seq(median(expr.sorted), max(expr.sorted), length.out = ncol(data.z)/2)
      
      ##### Combine both normal distributions to generate a bimodal distribution. Make sure the the length of this vector is equal to the number samples in the data
      data.x <- c(data.x1, data.x2)
      data.x <- data.x[1:ncol(data.z)]
      
      ##### Generate y-values for bimodal distribution
      data.y <- c(dnorm(data.x1, mean = mean(data.x1), sd = (max(data.x1)-mean(data.x1))/3), dnorm(data.x2, mean = mean(data.x2), sd = (max(data.x2)-mean(data.x2))/3))
      data.y <- data.y[1:ncol(data.z)]
      
      ##### Add bimodal dist values to the distribution dataframe
      data.df.dist <- rbind(data.df.dist, data.frame(gene = "Bimodal distribution", sample = colnames(data.z)[order(data.z)], expr = data.x, dens = density2freq(data.y)))
    }
    
    data.df <- rbind(data.df, data.df.dist)
    
    ##### Extract expression for selected sample in the distributions dataframe
    data.df.selected <- data.df[ sampleName == data.df$sample, ]
  }
  
  ##### Get min and max values based on the expression data
  den.x <- sort(data.df$expr)
  den.y <- sort(data.df$dens)
  
  ##### Assign colours to distributions
  genes.colour <- getColours(rev(unique(data.df$gene)))
  
  ##### Generate interactive density plot
  p <- plot_ly(data.df, x = ~expr, y = ~dens, type = 'scatter', mode = 'lines', color = ~gene, colors = genes.colour[[1]], width = 750, height = 200) %>%
    add_markers(y = data.df.selected$dens, x = data.df.selected$expr, 
                name = "Patient",
                text = "Patient",
                mode = 'markers',
                marker = list(size = 8, colors = data.df.selected$sample, color = rep(I("black"), each = nrow(data.df.selected)), line = list(color = "grey", width = 2)),
                showlegend = TRUE,
                inherit = FALSE) %>%
     layout(title = main_title,
           xaxis = list(title = x_title, range = c(den.x[1],den.x[length(den.x)])),
           yaxis = list (title = 'Weight', range = c(den.y[1],den.y[length(den.y)]), side = "right"),
           legend = list(orientation = 'h', y = 1.3))
  
  return( p )
  
  ##### Clean the space
  rm(gene, expr.sorted)
  rm(list = ls(pattern='^data*'))
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Generate box-plot for selected genes, highlighting samples of interest
barPlot <- function(gene, data, targets, y_title = "Counts", sampleName,  ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = NULL ) {

  ##### Used data for user-defined genes
  data <- data[ gene, ,drop=FALSE]
  
  ##### Prepare data frame
  targets$Target[ targets$Target==sampleName ] <- "Patient"
  rownames(targets)[ rownames(targets)==sampleName ] <- "Patient"
  data.df <- data.frame(targets$Target, rownames(targets), as.numeric(data))
  colnames(data.df) <- c("Group","Sample", "Data")
  
  ##### Reorder groups and add colours
  if ( !is.null(add_cancer) ) {
    data.df$Group <- factor(data.df$Group, levels=c( add_cancer, ext_cancer, int_cancer, "Patient"))
    group.colours <- c("forestgreen", "cornflowerblue", "red", "black")
  } else {
    data.df$Group <- factor(data.df$Group, levels=c(ext_cancer, int_cancer, "Patient"))
    group.colours <- c("cornflowerblue", "red", "black")
  }
  
  ##### The default order will be alphabetized unless specified as below
  data.df$Sample <- factor(data.df$Sample, levels = data.df[["Sample"]])
  p <- plot_ly(data.df, x = ~Sample, y = ~Data, color = ~Group, type = 'bar', colors = group.colours, width = 750, height = 200) %>%
    layout(title = "", xaxis = list( title = "", showticklabels = FALSE), yaxis = list(title = y_title), autosize = F, legend = list(orientation = 'h', y = 1.2), showlegend=TRUE)
  
  return( p )
  
  ##### Clean the space
  rm(list = ls(pattern='^data*'))
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Generate boxplot presenting expression profiles for selected set of genes
glanceExprPlot <- function(genes, data, targets, sampleName, int_cancer, ext_cancer, comp_cancer, add_cancer = NULL, hexcode, type = "z", sort = "diff", scaling = "gene-wise", report_dir) {
  
  if ( comp_cancer != int_cancer ) {
    targets <- targets[ targets$Target %!in% int_cancer, ]
    data <- data[ ,rownames(targets) ]
  }
  
  ##### Perform Z-score transformation of the median expression values
  if ( scaling == "gene-wise" ) {
    
    data.z <- t(scale(t(data)))
    y_title <- "mRNA expression (Z-score)"
    
    if ( type == "perc" ) {
      ##### Convert a expression values into corresponding percentiles
      data.z <- t(apply(data.z, 1, perc.rank))
      y_title <- "mRNA expression (percentile)"
    }
    
  } else {
    data.z <- scale(data, scale = FALSE)
    
    if ( type == "perc" ) {
      ##### Convert a expression values into corresponding percentiles
      data.z <- t(apply(data.z, 1, perc.rank))
    }
  }
  
  targets$Target[ targets$Target==sampleName ] <- "Patient"
  
  ##### Make sure that all genes are present in the expression matrix
  genes <- genes[ genes %in% rownames(data.z) ]
  
  ##### Genes sorting for visualisation
  ##### Sort genes by the greatest difference between the patient and the "comp_cancer" cohort
  if ( sort == "diff" ) {
    comp_cancer.medians <- rowMedians( data.z[ genes ,targets$Target==comp_cancer ] )
    names(comp_cancer.medians) <- genes
    comp_cancer.medians.diff <- comp_cancer.medians - data.z[ genes ,targets$Target=="Patient" ]
    genes <- genes[ order(comp_cancer.medians.diff) ]
  
  ##### Sort genes alphabetically
  } else if (sort == "alphabetically") {
    genes <- genes[ order(genes) ]
  }

  ##### Prepare dataframe for plotly
  gene.expr.df <- NULL
  
  for ( gene in genes ) {
    gene.expr.df <- rbind(gene.expr.df, data.frame(gene, targets$Target, data.z[gene, ]))
  }
  colnames(gene.expr.df) <- c("Gene", "Group", "Expression")
  
  ##### Reorder groups
  if ( !is.null(add_cancer) ) {
    gene.expr.df$Group <- factor(gene.expr.df$Group, levels=c("Patient", int_cancer, ext_cancer, add_cancer))
    group.colours <- c(I("black"), "red", "cornflowerblue", "forestgreen")
    
  } else {
    gene.expr.df$Group <- factor(gene.expr.df$Group, levels=c("Patient", int_cancer, ext_cancer))
    group.colours <- c(I("black"), "red", "cornflowerblue")
  }
  
  p <- plot_ly( gene.expr.df, x = ~Gene, y = ~Expression, color = ~Group, type = "box", colors = group.colours, opacity=0.3, showlegend = TRUE, width = 800, height = 400 ) %>% 
    add_markers(x = ~Gene[ gene.expr.df$Group %in% "Patient" ], y = ~Expression[ gene.expr.df$Group %in% "Patient" ], color = ~Group[ gene.expr.df$Group %in% "Patient" ], marker = list(size = 7), opacity=1, showlegend = FALSE) %>%
    
    layout(boxmode = "group", xaxis = list(title = ""), yaxis = list(title = y_title), legend = list( orientation = 'h', y = max(gene.expr.df$Expression), yancho = "top", bgcolor = "white"))
    
  ##### Create directory for "at glance" plots
  PlotsDir <- paste(report_dir, "glanceExprPlots", sep = "/")
    
  if ( !file.exists(PlotsDir) ) {
    dir.create(PlotsDir, recursive=TRUE)
  }
  
  ##### Save interactive plot as html file
  saveWidgetFix(p, file = paste(PlotsDir, paste0(hexcode, "_glance_expr_plot.", type, ".html"), sep = "/"))
  
  return( p )

  ##### Clean the space and return output
  rm(targets, data, sampleName, data.z, y_title, genes, comp_cancer.medians, comp_cancer.medians.diff, gene.expr.df, group.colours)
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Generate scatterplot with per-gene expression values (y-axis), CN values (x-axis) and mutation status info (colours), if provided
mutCNexprPlot <- function(data, alt_data = FALSE, cn_bottom = cn_bottom, cn_top = cn_top, comp_cancer, type = "z", report_dir) {
  
  ##### Extract info for genes to be annotated on the plot
  genes2annot <- data[ data$CN >= cn_top | data$CN <= cn_bottom ,]$Gene
  
  if ( length(genes2annot) == 0 ) {
    genes2annot <- ""
  }
  
  if ( type == "z" ) {
    names(data)[ names(data) %in% "Z_score_diff" ] <- "Expr"
    y_title <- paste0("mRNA expression (Z-score [Patient vs ", comp_cancer, "])")
      
  } else if ( type == "perc" ) {
    names(data)[ names(data) %in% "Perc_diff" ] <- "Expr"
    y_title <- paste0("mRNA expression (percentile [Patient vs ", comp_cancer, "])")
  }
  
  ##### Generate scatterplot with per-gene expression values (y-axis) (difference between Patient's and [comp_cancer] data), CN values (x-axis) and mutation status info (colours)
  if ( alt_data ) {
    p <- plot_ly(type='scatter', mode = "markers", width = 800, height = 600, showlegend = FALSE) %>%
      
      add_markers(data = data, y = ~Expr, x = ~CN, 
                name = ~Gene,
                text = paste0("Gene: ", data$Gene,  "\nAlterations: ", data$Alterations),
                mode = 'markers',
                marker = list(size=10, symbol="circle"),
                color = ~Gene,
                showlegend = TRUE,
                legendtitle=TRUE, 
                inherit = FALSE) %>%
      
      add_annotations( data = data[ data$CN >= cn_top | data$CN <= cn_bottom ,], text=genes2annot,
                      x=~CN, xanchor="left",
                      y=~Expr, yanchor="top",
                      font = list(color = "Grey", size = 10),
                      legendtitle=TRUE, showarrow=FALSE ) %>%
      
      layout( xaxis = list(title = "CN value"), yaxis = list(title = y_title), margin = list(l=50, r=50, b=50, t=50, pad=4), autosize = F, legend = list( orientation = 'v', x=1, y=0.97, yanchor="top"), showlegend=TRUE)
  
  ##### Generate scatterplot with per-gene expression values (y-axis) and CN values (x-axis)
  } else {
    p <- plot_ly(data, x = ~CN, y = ~Expr, text=~Gene, color = ~Gene, type='scatter', mode = "markers", marker = list(size=10, symbol="circle"), width = 800, height = 600) %>%
      
      add_annotations( data = data[ data$CN >= cn_top | data$CN <= cn_bottom ,], text=~Gene,
                      x=~CN, xanchor="left",
                      y=~Expr, yanchor="top",
                      font = list(color = "Grey",
                      size = 10),
                      legendtitle=TRUE, showarrow=FALSE ) %>%
      
      layout( xaxis = list(title = "CN value"), yaxis = list(title =  y_title), margin = list(l=50, r=50, b=50, t=50, pad=4), autosize = F, legend = list( orientation = 'v', y=0.8, yanchor="top"), showlegend=TRUE)
  }
  
  ##### Create directory for the plots
  mutCNexprPlotDir <- paste(report_dir, "cn_expr_plot", sep = "/")
  if ( !file.exists(mutCNexprPlotDir) ) {
    dir.create(mutCNexprPlotDir, recursive=TRUE)
  }
  
  ##### Save interactive plot as html file
  saveWidgetFix(p, file = paste(mutCNexprPlotDir, paste0("cn_expr_plot.", type, ".html"), sep = "/"))
    
  return( p )
  
  ##### Clean the space and return output
  rm(data, alt_data, genes2annot, y_title)
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Fusion visualisation 
arriba_plots <- function(arriba_file, arriba_results, results_dir) {

  ##### Get path to fusion visualisation  pdf file
  arriba_dir <- unlist(strsplit(arriba_file, split='/', fixed=TRUE))
  arriba_plots.pdf <- list.files(paste(arriba_dir[1:length(arriba_dir)-1], collapse = "/"), pattern="\\.pdf$")
  arriba_dir <- paste(arriba_dir[1:length(arriba_dir)-1], collapse = "/")
  arriba_plots.pdf <- paste(arriba_dir, arriba_plots.pdf, sep = "/")
    
  ##### Create directory for results
  if ( !file.exists(results_dir) ) {
    dir.create(results_dir, recursive=TRUE)
  }
  
  ##### Export pdf images to png
  for ( i in 1:nrow(arriba_results) ) {
    arriba_plots.png <- gsub(":", ".", paste0(results_dir, "/", make.names(paste(arriba_results$X.gene1[i], arriba_results$gene2[i], sep = "__")), "_", arriba_results$breakpoint1[i], "-", arriba_results$breakpoint2[i], ".png"))
    fusion <- pdf_render_page(arriba_plots.pdf, page = i, dpi = 300, numeric = TRUE, opw = "", upw = "")
    writePNG(fusion, arriba_plots.png)
  }

  ##### Clean the space
  rm(arriba_plots.pdf, arriba_plots.png, fusion)
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
}

##### Generate table with coloured cells indicating expression values for selected genes
exprTable <- function(genes, keep_all = FALSE, data, cn_data = NULL, sv_data = NULL, cn_decrease = TRUE, targets, sampleName, int_cancer, ext_cancer, comp_cancer, add_cancer = NULL, genes_annot = NULL, oncokb_annot = NULL, cancer_genes = NULL, mut_annot = NULL, fusion_genes = NULL, ext_links = FALSE, type = "z", scaling = "gene-wise") {
  
  ##### Check which of the selected genes are not present in the expression data
  genes.absent <- genes[ genes %!in% rownames(data) ]
    
  ##### Initiate dataframe for expression median values in each group
  targets.list <- unique(targets$Target)
  group.z <- as.data.frame(matrix(NA, ncol = length(targets.list), nrow = nrow(data)))
  colnames(group.z) <- targets.list
  rownames(group.z) <- rownames(data)
    
  ##### Perform scaling gene-wise
  if ( scaling == "gene-wise" ) {
    ##### Calculate z-score for each group  
    group.stats <- exprGroupsStats_geneWise(data, targets)[[1]]
    
    ##### Make sure to include only genes for which Z-scores were calaculated  (genes with SD = 0 across all samples will give NA)
    group.z <- group.z[ rownames(group.z) %in% rownames(group.stats[[targets.list[1]]]), ]
    
    #### Present expression data as percentiles or z-score values (default)
    for ( group in targets.list ) {
      if ( type == "perc" ) {
        group.z[, group] <- round(group.stats[[ group ]]$quantile, digits=1)
      } else {
        group.z[, group] <- round(group.stats[[ group ]]$z, digits=2)
      }
    }
    
  ##### Perform scaling group-wise
  } else {
    for ( group in targets.list ) {
      
      ##### Calculate z-score for each group  
      group.stats <- exprGroupStats_groupWise(data[rownames(group.z), ], targets, group)
      group.stats <- group.stats[order(rownames(group.stats)), ]
      
      #### Present expression data as percentiles or z-score values (default)
      if ( type == "perc" ) {
        group.z[, group] <- round(group.stats$quantile, digits=1)
      } else {
        group.z[, group] <- round(group.stats$z, digits=2)
      }
    } 
  }
  
  ##### If additional cancer type is defined then remove it from the data
  if ( !is.null(add_cancer) ) {
    group.z <- group.z[ , names(group.z) %!in% add_cancer ]
    targets <- targets[ targets$Target %!in% add_cancer, ]
    targets.list <- targets.list[ targets.list %!in% add_cancer ]
  }
  
  ##### Compute Z-scores sd for each gene across groups
  group.z <- cbind(group.z, round(rowSds(as.matrix(group.z)), digits = 2))
  names(group.z)[ncol(group.z)] <- "SD"
  
  ##### Calculate Z-score differneces between investigated sample and median values in the cancer group of interest
  group.z <- cbind(group.z, round((group.z[, sampleName] - group.z[, comp_cancer]), digits = 2))
  names(group.z)[ncol(group.z)] <- "Diff"
  
  ##### Add NAs for genes that are absent in the expression matrix. In the "Patient vs [comp_cancer]" columns provide "0"s to facilitate interactive sorting the table. These will appear in blank cells in the table
  if ( length(genes.absent) > 0 ) {
    
    NAs.df <- data.frame(matrix(NA, ncol = ncol(group.z), nrow = length(genes.absent)))
    names(NAs.df) <- names(group.z)
    rownames(NAs.df) <- genes.absent
    NAs.df[ names(NAs.df) %in% "Diff" ] <- 0
    group.z <- rbind( group.z,  NAs.df)
  }
  
  ##### Change sample ID to "Patient" for better visualisation
  names(group.z)[names(group.z)==sampleName] <- "Patient"
  targets.list[targets.list==sampleName] <- "Patient"
  
  ##### Reorder groups
  group.z <- cbind(group.z[ , c(ext_cancer, int_cancer, "Patient")], group.z[, c("SD", "Diff" )])
  
  ##### Add "Gene" column to facilitate adding annotations
  group.z$Gene <- rownames(group.z)
  
  ##### Add genes annotation
  if ( !is.null(genes_annot) ) {
    ##### Remove rows with duplicated gene symbols
    if ( "SYMBOL" %in% names(genes_annot) ) {
      genes_annot <- genes_annot[!duplicated(genes_annot$SYMBOL),]  
    }
    
    ##### Merge the dataframe with groups median expression values and gene annotations
    group.z <- merge(genes_annot, group.z, by.x="SYMBOL", by.y="Gene", all = TRUE, sort = FALSE)
    names(group.z) <- gsub("SYMBOL", "Gene", names(group.z))
  }
  
  ##### Define colours for cells background for each group and the patient vs [comp_cancer] difference
  ##### Initiate dataframe for expression median values in each group
  brks.q <- as.data.frame( matrix(NA, ncol = length(targets.list), nrow = length(seq(.05, .95, .0005)) ))
  colnames(brks.q) <- targets.list
  clrs.q <- as.data.frame( matrix(NA, ncol = length(targets.list), nrow = length(seq(.05, .95, .0005))+1 ))
  colnames(clrs.q) <- targets.list
  
  for ( group in c(targets.list, "Diff") ) {
    brks.q[[group]] <- quantile(group.z[, group], probs = seq(.05, .95, .0005), na.rm = TRUE)
    
    clrs_pos.q <- round(seq(255, 150, length.out = length(brks.q[[group]])/2 + 1.5), 0) %>%
    {paste0("rgb(255,", ., ",", ., ")")}
    clrs_neg.q <- rev(round(seq(255, 150, length.out = length(brks.q[[group]])/2 - 0.5), 0)) %>%
    {paste0("rgb(", .,",", .,",", "255)")}
    clrs.q[[group]] <- c(clrs_neg.q, clrs_pos.q)
  }
  
  ##### Subset the expression data to include only the user-defined genes
  group.z <- group.z[ group.z$Gene %in% genes, ]
    
  #### Add variants information to the expression table - if exists. Note, "TIER" and "CONSEQUENCE" columns are required
  if( !is.null(mut_annot) && "TIER" %in% colnames(mut_annot) && length(genes) > 0 ) {
    mut_annot <- mut_annot[mut_annot$SYMBOL %in% genes,]
    
    #### keep only varaints that has the lowest tier value. Multiple varaints detected in same gene but with higher tier will be added to additional column "CONSEQUENCE_OTHER". Applies to the ones that may have multiple mutations and hence tiers
    ##### First, create a list of genes to store multiple variants
    mut_consequence <- vector("list", length(unique(mut_annot$SYMBOL)))
    mut_consequence  <- setNames(mut_consequence,  unique(mut_annot$SYMBOL) )
    
    ##### Record all varaints detected in individual genes
    if ( nrow(mut_annot) > 0 ) {
      for ( i in 1:nrow(mut_annot) ) {
        mut_consequence[[ mut_annot$SYMBOL[i] ]] <- unique(c( mut_consequence[[ mut_annot$SYMBOL[i] ]], mut_annot$CONSEQUENCE[i] ))
      }
      
      mut_annot$CONSEQUENCE_OTHER <- "-"
    }
    
    ##### Remove the first elements since these variant consequences will be reported as the "canonical" CONSEQUENCE
    mut_consequence <- lapply(mut_consequence, function(x) x[-1])
    
    ##### Order variant entires based on tier info, to make sure that the varaints with the lowest tier are reported first
    mut_annot <- mut_annot[ order(mut_annot$TIER), ]
    
    ##### Remove rows with duplicated gene symbols
    mut_annot <- mut_annot[!duplicated(mut_annot$SYMBOL),]  
    rownames(mut_annot) <- mut_annot$SYMBOL
    
    ##### Add other provided variants consequences for individual genes
    for ( gene in rownames(mut_annot) ) {
      if ( length(mut_consequence[[ gene ]]) > 0 ) {
        mut_annot$CONSEQUENCE_OTHER[ match(gene, mut_annot$SYMBOL)  ] <- mut_consequence[[ gene ]]
      }
    }
    
    #### merge the variants information with the dataframe
    group.z <- merge(group.z, mut_annot, by.x = "Gene", by.y = "SYMBOL", all = TRUE, sort = FALSE)
  }
  
  ##### Add CN data if provided
  if ( !is.null(cn_data) ) {
    ##### Get the position of "Diff" column
    col_idx <- grep("Diff", names(group.z), fixed = TRUE)
    
    ##### Now place the CN data after the "Diff" column
    if ( length(genes) > 0 ) {
      group.z <- add_column(group.z, round(cn_data[ group.z$Gene, "CN"], digits=2), .after = col_idx)
      colnames(group.z)[ col_idx+1 ] <- "Patient (CN)"
      cn_range <- base::range(group.z[ ,"Patient (CN)" ], na.rm = TRUE)
      
    } else {
      group.z <- add_column(group.z, "", .after = col_idx)
      colnames(group.z)[ col_idx+1 ] <- "Patient (CN)"
      cn_range <- 0
    }
  }

  ##### Add structural variants results from MANTA
  if ( !is.null(sv_data) && length(genes) > 0 ) {
    ##### NOTE: when merging per-gene exprssion data with SV data from MANTA the "gene" column is used since multiple entires are possible for one gene in MANTA output
    group.z <- merge(group.z, sv_data, by.x="Gene", by.y="Gene", all = TRUE, sort = FALSE)
  }
  
  ##### Add info about known fusion genes
  if ( !is.null(fusion_genes) && length(genes) > 0 ) {
    
    group.z$Fusion_gene <- NA
    group.z$Fusion_gene[ group.z$Gene %in% fusion_genes  ] <- "Yes"
  }
  
  ##### Add cancer gene resources info
  if ( !is.null(cancer_genes) && length(genes) > 0 ) {
    group.z <- merge(group.z, cancer_genes, by.x="Gene", by.y="row.names", all = TRUE, sort = FALSE)
  }
  
  ##### Include only queried genes
  group.z <- group.z[ group.z$Gene %in% genes, ]
  group.z$SYMBOL <- group.z$Gene
  
  ##### Add links to external gene annotation resourses
  if ( ext_links && length(genes) > 0 ) {
    
    ##### Place the external links after the "Diff" column
    ##### Get the position of "Diff" column
    col_idx <- grep("Diff", names(group.z), fixed = TRUE)
    group.z <- add_column(group.z, NA, .after = col_idx)
    names(group.z)[ col_idx+1 ] <- "ext_links"
    
    for ( gene in genes ) {
      ##### Provide link to VICC meta-knowledgebase ( https://search.cancervariants.org )
      group.z$ext_links[ group.z$Gene==gene ] <- paste0("<a href='https://search.cancervariants.org/#", gene, "' target='_blank'>VICC</a>")
      
      ##### Provide link to OncoKB
      if ( !is.null(oncokb_annot) ) {
        if ( gene %in% rownames(oncokb_annot) & oncokb_annot[gene, "OncoKB"] == "Yes" ) {
          group.z$ext_links[ group.z$Gene == gene ] <- paste( group.z$ext_links[ group.z$Gene==gene ] , paste0("<a href='http://oncokb.org/#/gene/", gene, "' target='_blank'>OncoKB</a>"), sep = ", ")
        }
      }
      
      ##### Provide link to CIViC database druggable genes ( https://civicdb.org )
      if ( gene %in% caner_genes_annot.list[["civic_clin_evid"]]$gene ) {
        group.z$ext_links[ group.z$Gene==gene ] <- paste( group.z$ext_links[ group.z$Gene==gene ] , paste0("<a href='", unique(caner_genes_annot.list[["civic_clin_evid"]][ caner_genes_annot.list[["civic_clin_evid"]]$gene == gene , "gene_civic_url"]), "' target='_blank'>CIViC</a>"), sep = ", ")
      }
    }
    
    names(group.z) <- gsub("ext_links", "External resources", names(group.z))
  }
  
  ##### Attach links to GeneCards and Ensembl (if provided). Here we assume that gene names are
  for ( gene in genes ) {
    if ( "ENSEMBL" %in% names(group.z) ) {
        if ( !is.na(group.z$ENSEMBL[ group.z$Gene==gene ]) ) {
          
          group.z$ENSEMBL[ group.z$Gene==gene ] <- paste0("<a href='http://ensembl.org/Homo_sapiens/Gene/Summary?db=core;g=", group.z$ENSEMBL[ group.z$Gene==gene], "' target='_blank'>", group.z$ENSEMBL[ group.z$Gene == gene ], "</a>")
      }
    }
    
    group.z$Gene[ group.z$Gene==gene ] <- paste0("<a href='https://www.genecards.org/cgi-bin/carddisp.pl?gene=", gene, "' target='_blank'>", gene, "</a>")
  }

  ##### Order the data by CN values (to allow filtering based on CN information) and then by the highest absolute values for Patient vs [comp_cancer] difference (to allow filtering based on z-score differences)
  if ( !is.null(cn_data) && length(genes) > 0 ) {
    ##### Get the position of "Patient (CN)" column
    col_idx <- grep("Patient (CN)", names(group.z), fixed = TRUE)
    group.z <- group.z[ order(abs(group.z[, "Diff"]),  decreasing = TRUE), ]
    group.z <- group.z[ order(group.z[ ,col_idx ],  decreasing = cn_decrease), ]
    
  ##### Order the data by increasing TIER category (to allow filtering based on tier information) and then by the highest absolute values for "Diff" difference (to allow filtering based on z-score differences)
  } else if  ( !is.null(mut_annot) && length(genes) > 0 ) {
    group.z <- group.z[ order(abs(group.z[, "Diff"]),  decreasing = TRUE), ]
    group.z <- group.z[ order(group.z$TIER), ]
    
  ##### Order the data by MANTA increasing Tier (to prioritise SVs, based on https://github.com/AstraZeneca-NGS/simple_sv_annotation/blob/master/simple_sv_annotation.py), event type and then by the highest absolute values for Patient vs [comp_cancer] difference
  } else if  ( !is.null(sv_data) && length(genes) > 0 ) {
    group.z <- group.z[ order(abs(group.z[, "Diff"]),  decreasing = TRUE), ]
    group.z <- group.z[ order(group.z$"Fusion genes",  decreasing = TRUE), ]
    group.z <- group.z[ order(group.z$Tier), ]
    
  ##### Otherwise order table by the highest absolute values for Patient vs [comp_cancer] difference
  } else if ( length(genes) > 0 ) {
    group.z <- group.z[ order(abs(group.z[, "Diff"]),  decreasing = TRUE), ]
  }
  
  ##### Remove the internal reference cohort column if the patient samples origins from other tissue. Of note, the internal reference cohort was only used to process the in-house data (including the investigated patient sample) and to correct batch-effects
  if ( comp_cancer != int_cancer ) {
      group.z <- group.z[ , names(group.z) %!in% int_cancer ]
      targets.list[ match(int_cancer, targets.list) ] <- "Patient"
      
      ##### Get the position of "Diff" column
      diff_col_idx <- grep("Diff", names(group.z), fixed = TRUE)
      
  } else {
      ##### Get the position of "Diff" column
      diff_col_idx <- grep("Diff", names(group.z), fixed = TRUE)
      names(group.z)[ match("Diff", names(group.z)) ] <- paste0("Patient vs ", comp_cancer)
  }
  
  ##### Limit the ordered table to maximum of 2000 entries if "keep_all" is set to FALSE (default)
  if ( nrow(group.z) > 2000 && !keep_all ) {
    group.z <- group.z[ 1:2000, ]
  }
  
  ##### Define table height
  if ( nrow(group.z) == 2 ) {
    table_height <- 230
    scrollY <- "67px"
  } else {
    scrollY <- "167px"
    table_height <- 318
  }
  
  ##### Generate a table with genes annotations and coloured expression values in each group
  if ( !is.null(cn_data) ) {
    dt.table <- DT::datatable( data = group.z[, names(group.z) %!in% c("SYMBOL", "SD")], filter="none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrtip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, scrollCollapse = TRUE, deferRender = TRUE, scrollY = scrollY, scroller = TRUE), width = 800, height = table_height, caption = htmltools::tags$caption( style = 'caption-side: top; text-align: left; color:grey; font-size:100% ;'), escape = FALSE) %>%
      DT::formatStyle( columns = names(group.z)[names(group.z) %!in% c("SYMBOL", "SD")], `font-size` = '12px', 'text-align' = 'center' ) %>%
      
      ##### Colour cells according to the expression values quantiles in each group
      DT::formatStyle(columns = targets.list[1], 
                      backgroundColor = DT::styleInterval(brks.q[[targets.list[1]]], clrs.q[[targets.list[1]]])) %>%
      DT::formatStyle(columns = targets.list[2], 
                      backgroundColor = DT::styleInterval(brks.q[[targets.list[2]]], clrs.q[[targets.list[2]]])) %>%
      DT::formatStyle(columns = targets.list[3], 
                      backgroundColor = DT::styleInterval(brks.q[[targets.list[3]]], clrs.q[[targets.list[3]]])) %>%
      DT::formatStyle(columns = names(group.z)[diff_col_idx], 
                      backgroundColor = DT::styleInterval(brks.q[["Diff"]], clrs.q[["Diff"]])) %>%
      DT::formatStyle(columns = "Patient (CN)", background = DT::styleColorBar(cn_range, 'lightblue'), backgroundSize = '98% 88%', backgroundRepeat = 'no-repeat', backgroundPosition = 'center')
    
  ##### Generate a table with genes annotations and coloured expression values in each group
  } else {
    dt.table <- DT::datatable( data = group.z[, names(group.z) %!in% c("SYMBOL", "SD")], filter="none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrtip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, scrollCollapse = TRUE, deferRender = TRUE, scrollY = scrollY, scroller = TRUE), width = 800, height = table_height, caption = htmltools::tags$caption( style = 'caption-side: top; text-align: left; color:grey; font-size:100% ;'), escape = FALSE) %>%
      DT::formatStyle( columns = names(group.z)[names(group.z) %!in% c("SYMBOL", "SD")], `font-size` = '12px', 'text-align' = 'center' ) %>%
      
      ##### Colour cells according to the expression values quantiles in each group
      DT::formatStyle(columns = targets.list[1], 
                      backgroundColor = DT::styleInterval(brks.q[[targets.list[1]]], clrs.q[[targets.list[1]]])) %>%
      DT::formatStyle(columns = targets.list[2], 
                      backgroundColor = DT::styleInterval(brks.q[[targets.list[2]]], clrs.q[[targets.list[2]]])) %>%
      DT::formatStyle(columns = targets.list[3], 
                      backgroundColor = DT::styleInterval(brks.q[[targets.list[3]]], clrs.q[[targets.list[3]]])) %>%
      DT::formatStyle(columns = names(group.z)[diff_col_idx], 
                      backgroundColor = DT::styleInterval(brks.q[["Diff"]], clrs.q[["Diff"]]))
  }
  
  ##### Clean the space and return output
  rm(genes, data, cn_data, sv_data, targets, sampleName, genes_annot, oncokb_annot, cancer_genes, mut_annot, fusion_genes, genes.absent, targets.list, group.stats, brks.q, clrs.q)
  
  return( list(dt.table,  group.z) )
}

##### Generate table with drugs targeting selected set of genes using info from CIViC database (https://civicdb.org/)
civicDrugTable <- function(genes, civic_var_summaries, civic_clin_evid, evid_type = "Predictive", var_type = NULL) {
  
  ##### Initialize data frame to the about drug-target info from CIViC
  drug.info <- setNames(data.frame(matrix(ncol = 18, nrow = 0)), c("Gene", "Variant", "variant_types", "drugs", "nct_ids", "evidence_level", "evidence_type", "evidence_direction", "clinical_significance", "rating", "civic_actionability_score", "Disease", "phenotypes", "pubmed_id", "variant_origin", "representative_transcript", "representative_transcript2", "last_review_date"))
  
  evid_levels <- list("A" = "A: Validated association", "B" = "B: Clinical evidence", "C" = "C: Case study", "D" = "D: Preclinical evidence", "E" = "E: Inferential association")
  
  ##### Loop thourgh each gene and check if they are druggable
  for ( gene in genes) {
    ##### Get summary info about druggable genes
    if ( gene %in% civic_clin_evid$gene ) {
      ##### Extract info about all reported variants's clinical evidence for queried gene
      clin.evid.info <- civic_clin_evid[ civic_clin_evid$gene == gene , ]

      ##### Use more descriptive evidence level info
      for ( level in unique(clin.evid.info$evidence_level) ) {
        clin.evid.info$evidence_level[ clin.evid.info$evidence_level == level ] <- evid_levels[[ level ]]
      }
      
      ##### Subset table to include only variants with the evidence type of interest
      clin.evid.info <- clin.evid.info[ clin.evid.info$evidence_type == evid_type,  ]
        
      if ( nrow(clin.evid.info) > 0 ) {
        ##### Provide link to CIViC clinical evidence summary
        clin.evid.info$drugs <- paste0("<a href='", clin.evid.info$evidence_civic_url, "' target='_blank'>", clin.evid.info$drugs, "</a>")
        
        ##### Provide link to CIViC clinical evidence summary
        clin.evid.info$evidence_type <- paste0("<a href='", clin.evid.info$evidence_civic_url, "' target='_blank'>", clin.evid.info$evidence_type, "</a>")
        
        ##### Provide link to CIViC gene summary
        clin.evid.info$gene_civic_url <- paste0("<a href='", clin.evid.info$gene_civic_url, "' target='_blank'>", gene, "</a>")
        names(clin.evid.info)[ names(clin.evid.info) =="gene_civic_url" ] <- "Gene"
        
        ##### Provide link to CIViC variants summary
        clin.evid.info$variant_civic_url <- paste0("<a href='", clin.evid.info$variant_civic_url, "' target='_blank'>", clin.evid.info$variant, "</a>")
        names(clin.evid.info)[ names(clin.evid.info) =="variant_civic_url" ] <- "Variant"
        
        ##### Provide link to ClinicalTrials.gov variants summary based on NCT IDs
        for ( nct_id in clin.evid.info$nct_ids ) {
          if ( !is.empty(nct_id) ) {
            
            ##### Deal with multiple NCT IDs (separated by comma)
            nct_id_url <- gsub(" '" , "'", paste(gsub("/ " , "/", paste("<a href='https://clinicaltrials.gov/ct2/show/", unlist(strsplit(nct_id, split=",", fixed=TRUE)) , "' target='_blank'>", unlist(strsplit(nct_id, split=",", fixed=TRUE)), "</a>")), collapse = ", "))
            clin.evid.info$nct_ids[ clin.evid.info$nct_ids==nct_id ] <- nct_id_url
          }
        }
        
        ##### Provide link to PubMed variants summary
        clin.evid.info$pubmed_id <- paste0("<a href='https://www.ncbi.nlm.nih.gov/pubmed/", clin.evid.info$pubmed_id, "' target='_blank'>", clin.evid.info$pubmed_id, "</a>")
        
        ##### Provide link to Disease Ontology
        clin.evid.info$doid <- paste0("<a href='http://www.disease-ontology.org/?id=DOID:", clin.evid.info$doid, "' target='_blank'>", clin.evid.info$disease, "</a>")
        names(clin.evid.info)[ names(clin.evid.info) =="doid" ] <- "Disease"
        
        ##### Extract info about all variants it that gene
        var.info <- civic_var_summaries[ civic_var_summaries$gene == gene , ]
        var.info <- var.info[, c("variant", "variant_types", "civic_actionability_score")]
        var.info[,"variant_types"] <- gsub("_", " ", var.info[,"variant_types"])
        var.info[,"variant_types"] <- gsub(",", ", ", var.info[,"variant_types"])
        
        ##### Merge about all variants it that gene and clinical evidence info
        clin.evid.info <- merge(clin.evid.info, var.info, by = "variant", all.x = TRUE)
        
        ##### Filter drug matching info depending on the variant type
        var_type.keep <- NULL
        
        ##### Remove entries containing "EXPRESSION", "AMPLIFICATION", "DELETION", "METHYLATION", "WILD TYPE", "FUSION", "COPY", "REARRANGEMENT", "PHOSPHORYLATION", "TRANSCRIPT", "GAIN", "LOSS"
        if ( !is.null(var_type) && var_type == "mutation" ) {
          var_type.keep <- c(var_type.keep, grep( "EXPRESSION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "AMPLIFICATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "DELETION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "METHYLATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "WILD TYPE", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "FUSION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "REARRANGEMENT", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "PHOSPHORYLATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "COPY", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "TRANSCRIPT", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "GAIN", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "LOSS", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          
          clin.evid.info <- clin.evid.info[ -c(unique(var_type.keep)), ]
          
        ##### Keep only entries containing "EXPRESSION", "FUSION", "TRANSCRIPT", "ALTERATION"
        } else if ( !is.null(var_type) && var_type == "expression" ) {
          var_type.keep <- c(var_type.keep, grep( "EXPRESSION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "FUSION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "TRANSCRIPT", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "ALTERATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          
          clin.evid.info <- clin.evid.info[ c(unique(var_type.keep)), ]
          
        ##### Keep only entries containing "FUSION", "ALTERATION", "[gene]-", "-[gene]"
        } else if ( !is.null(var_type) && var_type == "fusion" ) {
          var_type.keep <- c(var_type.keep, grep( "FUSION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( paste0(gene, "-"), clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( paste0("-", gene), clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "ALTERATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          
          clin.evid.info <- clin.evid.info[ c(unique(var_type.keep)), ]
        
        ##### Keep only entries containing "AMPLIFICATION", "COPY", "GAIN", "ALTERATION"
        } else if ( !is.null(var_type) && var_type == "copy_gain" ) {
          var_type.keep <- c(var_type.keep, grep( "AMPLIFICATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "COPY", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "GAIN", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "ALTERATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          
          clin.evid.info <- clin.evid.info[ c(unique(var_type.keep)), ]
        
        ##### Keep only entries containing "DELETION", "COPY", "LOSS", "ALTERATION"
        } else if ( !is.null(var_type) && var_type == "copy_loss" ) {
          var_type.keep <- c(var_type.keep, grep( "DELETION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "COPY", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "LOSS", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          var_type.keep <- c(var_type.keep, grep( "ALTERATION", clin.evid.info$variant, invert=FALSE, ignore.case=TRUE))
          
          clin.evid.info <- clin.evid.info[ c(unique(var_type.keep)), ]
        }
      }
      
      if ( nrow(clin.evid.info) > 0 ) {
        ##### Subset table to include only most important info
        clin.evid.info <- clin.evid.info[ , names(drug.info)]
        
        ##### Add drugs info for subsequent gene
        drug.info <- rbind(drug.info, clin.evid.info)
      }
    }
  }
  
  ##### Use more friendly column names for the table
  names(drug.info) <- c("Gene", "Variant", "Variant type", "Drugs", "Clinical trials", "Evidence level", "Evidence type", "Evidence direction", "Clinical significance", "Trust rating", "Actionability score", "Disease", "Phenotypes", "PubMed ID",  "Variant origin", "Representative transcript", "Representative transcript 2", "Review date")
  
  ##### Limit the info to fewer columns
  drug.info <- drug.info[ , c("Gene", "Variant", "Variant type", "Drugs", "Clinical trials", "Evidence level", "Evidence direction", "Clinical significance", "Trust rating", "Actionability score", "Disease", "Phenotypes", "PubMed ID",  "Representative transcript", "Representative transcript 2")] 
  
  ##### Generate a table
  dt.table <- DT::datatable( data = drug.info, filter = "none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrtip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, deferRender = TRUE, scrollY = "167px", scroller = TRUE), width = 800, caption = htmltools::tags$caption(style = 'caption-side: top; text-align: left; color:grey; font-size:100% ;'), escape = FALSE) %>%
    DT::formatStyle( columns = names(drug.info), `font-size` = '12px', 'text-align' = 'center' ) %>%
    ##### Colour cells according to evidence level and trust rating
    DT::formatStyle(columns = "Evidence level", 
                    backgroundColor = DT::styleEqual(c("A: Validated association", "B: Clinical evidence", "C: Case study", "D: Preclinical evidence", "E: Inferential association"), c("mediumseagreen", "deepskyblue", "mediumpurple", "darkorange", "coral")) )  %>%
    DT::formatStyle(columns = "Trust rating", 
                    backgroundColor = DT::styleEqual(c(1:5), c("coral", "azure", "lightskyblue", "palegreen", "mediumseagreen")) )
  
  ##### Clean the space and return output
  rm(genes, civic_var_summaries, civic_clin_evid, evid_levels, clin.evid.info, var.info, var_type.keep)
  return( list(dt.table,  drug.info) )
}

##### Code from UMCCRISE to prioritise SV events (version for "-sv-prioritize-manta-pass.tsv" files, https://github.com/umccr/umccrise/blob/master/umccrise/rmd_files/index.Rmd)
sv_prioritize_short <- function(sv_file) {
  
  sv_all = NULL
  
  if (length(readLines(con = sv_file, n = 2)) > 1) {
    sv_all <- readr::read_tsv(sv_file, col_names = TRUE) %>%
      tidyr::unnest(annotation = strsplit(annotation, ',')) %>% # Unpack multiple annotations per region
      tidyr::separate(annotation,
                      c('Event', 'Annotation', 'Gene', 'Transcript', 'Priority', 'Tier'),
                      sep = '\\|', convert = TRUE) %>% # Unpack annotation columns %>%
      dplyr::mutate(start = format(start, big.mark = ',', trim = T),
                    end = format(end, big.mark = ',', trim = T)) %>% 
      dplyr::mutate(Location = str_c(chrom, ':', start, sep = ''),
                    Location = ifelse(is.na(end), Location, str_c(Location))) %>%
      dplyr::mutate(SR = split_read_support, PR = paired_support_PR) %>%
      dplyr::select(Location, Gene, Priority, Tier, Annotation, Event, SR, PR) %>%
      dplyr::distinct()
      # dplyr::mutate(Chrom = factor(Chrom, levels = c(1:22, "X", "Y", "MT")))
  } else {
    warning('No prioritized events detected')
  }
  return( sv_all )
}

##### Code from UMCCRISE to prioritise SV events (version for "-manta.tsv" files https://github.com/umccr/umccrise/blob/master/umccrise/rmd_files/index.Rmd
sv_prioritize <- function(sv_file) {
  
  sv_all = NULL

  if (length(readLines(con = sv_file, n = 2)) > 1) {
    
    ##### Due to changes in PURPLE output format there are two expected column names combinations
    if ( all(c("AF_BPI", "AF_PURPLE", "CN_PURPLE", "CN_change_PURPLE", "Ploidy_PURPLE") %in% names(read_tsv(sv_file, col_names = TRUE))) ) {
    
      sv_all <- readr::read_tsv(sv_file, col_names = TRUE) %>%
        dplyr::select(-caller, -sample) %>% 
        split_sv_field(AF_BPI, is_pct = T) %>% 
        split_sv_field(AF_PURPLE, is_pct = T) %>% 
        split_sv_field(CN_PURPLE) %>% 
        split_sv_field(CN_change_PURPLE) %>% 
        dplyr::mutate(
          Ploidy_PURPLE = as.double(Ploidy_PURPLE),
          Ploidy_PURPLE = format(Ploidy_PURPLE, nsmall = 2)
        ) %>% 
        tidyr::separate(split_read_support, c("SR (ref)", "SR (alt)"), ",") %>% 
        dplyr::mutate(SR = as.integer(`SR (alt)`)) %>% 
        tidyr::separate(paired_support_PR, c("PR (ref)", "PR (alt)"), ",") %>% 
        dplyr::mutate(PR = as.integer(`PR (alt)`)) %>% 
        tidyr::separate(paired_support_PE, c("PE (ref)", "PE (alt)"), ",") %>% 
        dplyr::mutate(PE = as.integer(`PE (alt)`)) %>% 
        
        dplyr::filter(svtype != 'BND' | is.na(SR) | PR>SR) %>%  # remove BND with split read support higher than paired
        tidyr::unnest(annotation = strsplit(annotation, ',')) %>%  # Unpack multiple annotations per region
        tidyr::separate(annotation,
                        c('Event', 'Effect', 'Genes', 'Transcript', 'Detail', 'Tier'),
                        sep = '\\|', convert = TRUE) %>%  # Unpack annotation columns
        dplyr::mutate(start = format(start, big.mark = ',', trim = T),
                      end = format(end, big.mark = ',', trim = T)) %>% 
        dplyr::mutate(location = str_c(chrom, ':', start, sep = ''),
                      location = ifelse(is.na(end), location, str_c(location))) %>% 
        dplyr::arrange(Tier, Effect, desc(AF_PURPLE), Genes) %>% 
        dplyr::mutate(Gene = subset_genes(Genes, c(1, 2)),
                      Gene = ifelse((str_split(Genes, '&') %>% map_int(length)) > 2,
                                    str_c(Gene, '...', sep = ', '),
                                    Gene),
                      `Other affected genes` = subset_genes(Genes, -c(1,2)) %>% str_replace_all('&', ', '),
                      Gene = ifelse(str_detect(Effect, "gene_fusion"),
                                    Gene,
                                    Gene %>% str_replace_all('&', ', '))
                      ) %>% 
        separate(Effect, c("Effect", "Other effects"), sep = '&') %>% 
        dplyr::select(Tier = tier, Event = svtype, Gene, Effect = Effect, Detail = Detail, Location = location, AF = AF_PURPLE, `CN chg` = CN_change_PURPLE, SR, PR, CN = CN_PURPLE, Ploidy = Ploidy_PURPLE, PURPLE_status, `SR (ref)`, `PR (ref)`, PE, `PE (ref)`, `Somatic score` = somaticscore, Transcript = Transcript, `Other effects`, `Other affected genes`, `AF at breakpoint 1` = AF_PURPLE1, `AF at breakpoint 2` = AF_PURPLE2, `CN at breakpoint 1` = CN_PURPLE1, `CN at breakpoint 2` = CN_PURPLE2, `CN change at breakpoint 1` = CN_change_PURPLE1, `CN change at breakpoint 2` = CN_change_PURPLE2, `AF before adjustment, bp 1` = AF_BPI1, `AF before adjustment, bp 2` = AF_BPI2
        ) %>%
        dplyr::distinct()
        # dplyr::mutate(chr = factor(chr, levels = c(1:22, "X", "Y", "MT"))) %>%
      
    } else {
         sv_all <- readr::read_tsv(sv_file, col_names = TRUE) %>%
        dplyr::select(-caller, -sample) %>% 
        split_sv_field(BPI_AF, is_pct = T) %>% 
        split_sv_field(AF, is_pct = T) %>% 
        split_sv_field(CN) %>% 
        split_sv_field(CN_change) %>% 
        dplyr::mutate(
          Ploidy = as.double(Ploidy),
          Ploidy = format(Ploidy, nsmall = 2)
        ) %>% 
        tidyr::separate(split_read_support, c("SR (ref)", "SR (alt)"), ",") %>% 
        dplyr::mutate(SR = as.integer(`SR (alt)`)) %>% 
        tidyr::separate(paired_support_PR, c("PR (ref)", "PR (alt)"), ",") %>% 
        dplyr::mutate(PR = as.integer(`PR (alt)`)) %>% 
        tidyr::separate(paired_support_PE, c("PE (ref)", "PE (alt)"), ",") %>% 
        dplyr::mutate(PE = as.integer(`PE (alt)`)) %>% 
        
        dplyr::filter(svtype != 'BND' | is.na(SR) | PR>SR) %>%  # remove BND with split read support higher than paired
        tidyr::unnest(annotation = strsplit(annotation, ',')) %>%  # Unpack multiple annotations per region
        tidyr::separate(annotation,
                        c('Event', 'Effect', 'Genes', 'Transcript', 'Detail', 'Tier'),
                        sep = '\\|', convert = TRUE) %>%  # Unpack annotation columns
        dplyr::mutate(start = format(start, big.mark = ',', trim = T),
                      end = format(end, big.mark = ',', trim = T)) %>% 
        dplyr::mutate(location = str_c(chrom, ':', start, sep = ''),
                      location = ifelse(is.na(end), location, str_c(location))) %>% 
        dplyr::arrange(Tier, Effect, desc(AF), Genes) %>% 
        dplyr::mutate(Gene = subset_genes(Genes, c(1, 2)),
                      Gene = ifelse((str_split(Genes, '&') %>% map_int(length)) > 2,
                                    str_c(Gene, '...', sep = ', '),
                                    Gene),
                      `Other affected genes` = subset_genes(Genes, -c(1,2)) %>% str_replace_all('&', ', '),
                      Gene = ifelse(str_detect(Effect, "gene_fusion"),
                                    Gene,
                                    Gene %>% str_replace_all('&', ', '))
                      ) %>% 
        separate(Effect, c("Effect", "Other effects"), sep = '&') %>% 
        dplyr::select(Tier = tier, Event = svtype, Gene, Effect = Effect, Detail = Detail, Location = location, AF, `CN chg` = CN_change, SR, PR, CN, Ploidy, PURPLE_status, `SR (ref)`, `PR (ref)`, PE, `PE (ref)`, `Somatic score` = somaticscore, Transcript = Transcript, `Other effects`, `Other affected genes`, `AF at breakpoint 1` = AF1, `AF at breakpoint 2` = AF2, `CN at breakpoint 1` = CN1, `CN at breakpoint 2` = CN2, `CN change at breakpoint 1` = CN_change1, `CN change at breakpoint 2` = CN_change2, `AF before adjustment, bp 1` = BPI_AF1, `AF before adjustment, bp 2` = BPI_AF2
        ) %>%
        dplyr::distinct()
        # dplyr::mutate(chr = factor(chr, levels = c(1:22, "X", "Y", "MT"))) %>%
    }
  } else {
    warning('No prioritized events detected')
  }
  return( sv_all )
}

##### Function used in the "sv_prioritize" function
subset_genes = function(genes, ind) {
  genes %>% str_split('&') %>% map(~ .[ind] %>% replace("", NA) %>% .[!is.na(.)]) %>% map_chr(~ ifelse(length(.) > 0, str_c(., collapse = '&'), ""))
}

##### Function used in the "sv_prioritize" function
format_val = function(val, is_pct = F) {
  ifelse(!is.na(val), 
         format(val,  digits = 1) %>% str_c(ifelse(is_pct, "%", "")), NA)
}

##### Function used in the "sv_prioritize" function 
split_sv_field = function(.data, field, is_pct = F) {
  f_q = rlang::enquo(field)
  f_str = rlang::quo_name(f_q)
  f1_str = str_c(f_str, '1')
  f2_str = str_c(f_str, '2')
  f1_q = sym(f1_str)
  f2_q = sym(f2_str)
  .data %>% 
    separate(!!f_q, c(f1_str, f2_str), ",") %>% 
    dplyr::mutate(
      !!f1_q := as.double(!!f1_q) * ifelse(is_pct, 100, 1),
      !!f2_q := as.double(!!f2_q) * ifelse(is_pct, 100, 1),
      !!f_q  := (!!f1_q + ifelse(is.na(!!f2_q), !!f1_q, !!f2_q)) / 2,
      !!f_q  := format_val(!!f_q, is_pct),
      !!f1_q := format_val(!!f1_q, is_pct),
      !!f2_q := format_val(!!f2_q, is_pct)
    )
}

CapStr <- function(y) {
  c <- strsplit(y, " ")[[1]]
  paste(toupper(substring(c, 1,1)), substring(c, 2),
      sep="", collapse=" ")
}

##### A wrapper to saveWidget which compensates for arguable BUG in saveWidget which requires `file` to be in current working directory (see post https://github.com/ramnathv/htmlwidgets/issues/299 )
saveWidgetFix <- function ( widget, file, ...) {
  wd<-getwd()
  on.exit(setwd(wd))
  outDir<-dirname(file)
  file<-basename(file)
  setwd(outDir);
  htmlwidgets::saveWidget(widget,file=file,...)
}

##### Define function for generating spider web plots to present immunogram genes (code from http://www.statisticstoproveanything.com/2013/11/spider-web-plots-in-r.html)
# data - data.frame or matrix
# data.row - row of data to plot (if NULL uses row 1)
# y.cols - columns of interest (if NULL it selects all numeric columns)
# main - title of plot (if NULL then rowname of data)
# add - whether the plot should be added to an existing plot
# col - color of the data line
# lty - lty of the data line

webplot = function(data, data.row = NULL, y.cols = NULL, main = NULL, add = F, 
    col = "red", lty = 1, scale = T) {
    if (!is.matrix(data) & !is.data.frame(data)) 
        stop("Requires matrix or data.frame")
    if (is.null(y.cols)) 
        y.cols = colnames(data)[sapply(data, is.numeric)]
    if (sum(!sapply(data[, y.cols], is.numeric)) > 0) {
        out = paste0("\"", colnames(data)[!sapply(data, is.numeric)], "\"", 
            collapse = ", ")
        stop(paste0("All y.cols must be numeric\n", out, " are not numeric"))
    }
    if (is.null(data.row)) 
        data.row = 1
    if (is.character(data.row)) 
        if (data.row %in% rownames(data)) {
            data.row = which(rownames(data) == data.row)
        } else {
            stop("Invalid value for data.row:\nMust be a valid rownames(data) or row-index value")
        }
    if (is.null(main)) 
        main = rownames(data)[data.row]
    if (scale == T) {
        data = scale(data[, y.cols])
        data = apply(data, 2, function(x) x/max(abs(x)))
    }
    data = as.data.frame(data)
    n.y = length(y.cols)
    min.rad = 360/n.y
    polar.vals = (90 + seq(0, 360, length.out = n.y + 1)) * pi/180

    if (add == F) {
        plot(0, xlim = c(-2.2, 2.2), ylim = c(-2.2, 2.2), type = "n", axes = F, 
            xlab = "", ylab = "")
        title(main)
        lapply(polar.vals, function(x) lines(c(0, 2 * cos(x)), c(0, 2 * sin(x))))
        lapply(1:n.y, function(x) text(2.15 * cos(polar.vals[x]), 2.15 * sin(polar.vals[x]), 
            y.cols[x], cex = 0.8))

        lapply(seq(0.5, 2, 0.5), function(x) lines(x * cos(seq(0, 2 * pi, length.out = 100)), 
            x * sin(seq(0, 2 * pi, length.out = 100)), lwd = 0.5, lty = 2, col = "gray60"))
        lines(cos(seq(0, 2 * pi, length.out = 100)), sin(seq(0, 2 * pi, length.out = 100)), 
            lwd = 1.2, col = "gray50")
    }

    r = 1 + data[data.row, y.cols]
    xs = r * cos(polar.vals)
    ys = r * sin(polar.vals)
    xs = c(xs, xs[1])
    ys = c(ys, ys[1])
    lines(xs, ys, col = col, lwd = 2, lty = lty)
    
    #### Clear plots to free up some memory
    if(!is.null(dev.list())) invisible(dev.off())
}
##### Generate a full-resolution pdf image before generating a small image in the chunk
knitr::knit_hooks$set(plot = allow_thumbnails)
##### Load libraries
suppressMessages(library(edgeR))
suppressMessages(library(limma))
suppressMessages(library(EDASeq))
suppressMessages(library(preprocessCore))
suppressMessages(library(rapportools))
suppressMessages(library(tximport))
suppressMessages(library(rhdf5))
suppressMessages(library(openxlsx))
suppressMessages(library(readr))
suppressMessages(library(tidyverse))
suppressMessages(library(dplyr))
suppressMessages(library(tidyr))
suppressMessages(library(rlang))
suppressMessages(library(DT))
suppressMessages(library(matrixStats))
suppressMessages(library(tibble))
suppressMessages(library(knitr))
suppressMessages(library(scales))
suppressMessages(library(RCircos))
suppressMessages(library(ggplot2))
suppressMessages(library(ggforce))
suppressMessages(library(pdftools))
suppressMessages(library(png))
suppressMessages(library(htmltools))
suppressMessages(library(htmlwidgets))
suppressMessages(library(devtools))
suppressMessages(library(lares))
suppressMessages(library(package=paste0("EnsDb.Hsapiens.v", params$ensembl_version), character.only = TRUE))
suppressMessages(library(package=paste0("BSgenome.Hsapiens.UCSC.hg", params$ucsc_genome_assembly), character.only = TRUE))
##### Define Z-transformation direction
if (tolower(params$scaling) == "gene-wise"){
  scaling <- "gene-wise"
} else {
  scaling <- "group-wise"
}
##### Annotate transcripts with gene IDs
edb <- eval(parse(text = paste0("EnsDb.Hsapiens.v", params$ensembl_version)))
  
##### Get keytypes for gene SYMBOL
keys <- keys(edb, keytype="GENEID")
  
##### Get genes genomic coordiantes
tx2ensembl <- ensembldb::select(edb, keys=keys, columns=c("TXID", "GENEID"), keytype="GENEID")
names(tx2ensembl) <- gsub("TXID", "tx_name", names(tx2ensembl))
names(tx2ensembl) <- gsub("GENEID", "gene_id", names(tx2ensembl))
  
##### Clean the space
rm(edb, keys)
##### Load reference datasets
##### Define the reference datasets based on user-defined input
dataset <- toupper(params$dataset)

ref_dataset <- list( "ext_ref" = c(paste0(params$ref_data_dir, "/ref_data/TCGA_", strsplit(dataset, split='-', fixed=TRUE)[[1]][1], "_Counts.exp.gz"), paste0(params$ref_data_dir, "/ref_data/TCGA_", dataset, "_Target.txt"), paste0(strsplit(dataset, split='-', fixed=TRUE)[[1]][1], " (TCGA)")),
                     "int_ref" = c(paste0(params$ref_data_dir, "/ref_data/UMCCR_PDAC_Counts.exp.gz"), paste0(params$ref_data_dir, "/ref_data/UMCCR_PDAC_Target.txt"), "PAAD (UMCCR)")
)

##### Create a list with reference datasets
ref_dataset.list <- vector("list", length(dataset))
names(ref_dataset.list) <- dataset

##### Create a list with various sets of genes
ref_genes <- c("genes_cancer", "genes_oncokb", "genes_immune", "genes_hrd")
ref_genes.list <- vector("list", length(ref_genes))
names(ref_genes.list) <- ref_genes

##### Create a list with cancer genes annotations
caner_genes_annot <- c("oncokb_clin_vars", "oncokb_all_vars")
caner_genes_annot.list <- vector("list", length(caner_genes_annot))
names(caner_genes_annot.list) <- caner_genes_annot

##### Get the subject ID
if ( !is.na(params$subject_id) ) {
  subjectID <- params$subject_id
} else {
  subjectID <- ""
}

if ( !is.null(params$bcbio_rnaseq) ) {
  
  ##### Get patient data dir and sample file name
  dataDir <- params$bcbio_rnaseq
  
  ##### Look at countsFromAbundance parameter to change the method to generate the counts
  txi.kallisto <- tximport(paste0(dataDir, "/kallisto/abundance.tsv"), type = "kallisto", tx2gene = tx2ensembl)
  
  ##### Extract kallisto counts to prepare dataframe
  counts <- as.data.frame(txi.kallisto$counts) %>%
    tibble::rownames_to_column() %>%
    dplyr::rename(count = V1)
  
} else if ( !is.null(params$dragen_rnaseq) ) {
  
  ##### Get patient data dir and sample file name
  dataDir <- paste(params$dragen_rnaseq, "dragen", sep = "/")
  
  ##### Look at countsFromAbundance parameter to change the method to generate the counts
  txi.salmon <- tximport(paste0(dataDir, "/", list.files(dataDir, pattern="\\.sf$")), type = "salmon", tx2gene = tx2ensembl)
  
  ##### Extract salmon counts to prepare dataframe
  counts <- as.data.frame(txi.salmon$counts) %>%
    tibble::rownames_to_column() %>%
    dplyr::rename(count = V1)
}

##### Create directory for results
results_dir <- paste0(params$report_dir, "/", params$sample_name, params$dataset_name_incl, ".results")

if ( !file.exists(results_dir) ) {
  dir.create(results_dir, recursive=TRUE)
}

##### Check if spreadsheet with clinical information exists
clinical_info_file <- params$clinical_info
runClinicalChunk <- FALSE

if ( file.exists(clinical_info_file) ) {
  ref_dataset.list[[dataset]][["clinical_info"]] <- read.xlsx(xlsxFile = clinical_info_file, sheet = 1, colNames = TRUE, rowNames = FALSE, detectDates = TRUE, skipEmptyRows = TRUE, skipEmptyCols = TRUE, check.names = TRUE)
  runClinicalChunk <- TRUE
}

##### Read in selected genes list
ref_genes.list[["genes_cancer"]] <- read.table(paste(params$ref_data_dir, params$genes_cancer, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="")
ref_genes.list[["genes_oncokb"]] <- read.table(paste(params$ref_data_dir, params$oncokb_genes, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="", comment.char = "")
ref_genes.list[["genes_immune"]]$immune_markers <- read.table(paste(params$ref_data_dir, params$genes_immune_markers, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="")
ref_genes.list[["genes_hrd"]] <- read.table(paste(params$ref_data_dir, params$genes_hrd, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="")

if ( params$immunogram ) {
  ref_genes.list[["genes_immune"]]$immunogram <- read.table(paste(params$ref_data_dir, params$genes_immunogram, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="")
}

##### Read in gene fusion data for investigate sample
##### Read in arriba and pizzly fusion calls
##### Check if arriba output file exists
arriba_file <- paste(dataDir, "arriba", "fusions.tsv", sep = "/")
arriba_pdf <- paste(dataDir, "arriba", "fusions.pdf", sep = "/")
runArribaChunk <- FALSE
runFusionChunk <- FALSE

if ( file.exists(arriba_file) ) {
  ref_genes.list[["arriba"]] <- read.table(file = arriba_file, header = TRUE, comment.char = "", quote = "")
  
  ##### Make sure that at least one fusions has been reported by Arriba
  if ( nrow(ref_genes.list[["arriba"]]) > 0 ) {
    
    ##### Convert Arriba pdf booklet with fusion plots to png images
    if ( file.exists(arriba_pdf) ) {
      arriba_plots(arriba_file = arriba_file, arriba_results = ref_genes.list[["arriba"]], results_dir = paste0(results_dir, "/arriba"))
    }
    
    ##### Write list of fusion events for which Arriba plot is available into a file (for PIEdb portal)
    fusion <- gsub(":", ".", c("", paste0(make.names(paste(ref_genes.list[["arriba"]]$X.gene1, ref_genes.list[["arriba"]]$gene2, sep = "__")), "_", ref_genes.list[["arriba"]]$breakpoint1, "-", ref_genes.list[["arriba"]]$breakpoint2)))
    
    write.table(prepare2write(fusion), file = paste0(results_dir, "/", params$sample_name, params$dataset_name_incl, ".RNAseq_report.arriba_fusions.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=FALSE, append = FALSE )
  
    runArribaChunk <- TRUE
    runFusionChunk <- TRUE
    
  } else {
    ##### Write list of fusion events for which arriba plot is available into a file (for PIEdb portal)
  write.table(prepare2write(""), file = paste0(results_dir, "/", params$sample_name, params$dataset_name_incl, ".RNAseq_report.arriba_fusions.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=FALSE, append = FALSE )
  }
  
} else {
  ##### Write list of fusion events for which arriba plot is available into a file (for PIEdb portal)
  write.table(prepare2write(""), file = paste0(results_dir, "/", params$sample_name, params$dataset_name_incl, ".RNAseq_report.arriba_fusions.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=FALSE, append = FALSE )
}

##### Read in dragen fusion calls
##### Check if dragen output file exists
dragen_fusion_file <- paste(dataDir, list.files(dataDir, pattern="\\.fusion_candidates.final$"), sep = "/")
runDragenFusionChunk <- FALSE

if ( !is.null(params$dragen_rnaseq) && file.exists(dragen_fusion_file) ) {
  
  ##### Dragen's fusion output file header starts with '#' hence change the comment indicator option to '^' ( https://stackoverflow.com/questions/27196470/reading-a-line-that-starts-with-a-hash-on-a-txt-file )
  
  dragen_fusion <- read.table(file = dragen_fusion_file[1], header = TRUE, comment.char = '^', quote = "")
  
  ##### Check Dragen's fusion format version
  #####  Dragen's fusion format version 3.9.3
  if ( all(c("X.FusionGene", "Score", "LeftBreakpoint", "RightBreakpoint", "Gene1Location", "Gene2Location", "Gene1Sense", "Gene2Sense", "Gene1Id", "Gene2Id", "NumSplitReads", "NumSoftClippedReads", "NumPairedReads", "ReadNames") %in% colnames(dragen_fusion)) ) {
    colnames(dragen_fusion) <- c("FusionGene", "Score", "LeftBreakpoint", "RightBreakpoint", "Gene1Location", "Gene2Location", "Gene1Sense", "Gene2Sense", "Gene1Id", "Gene2Id", "NumSplitReads", "NumSoftClippedReads", "NumPairedReads", "ReadNames")
  } else if ( all(c("X.FusionGene", "Score", "LeftBreakpoint", "RightBreakpoint", "ReadNames") %in% colnames(dragen_fusion)) ) {
    colnames(dragen_fusion) <- c("FusionGene", "Score", "LeftBreakpoint", "RightBreakpoint", "ReadNames")
  }
  
  dragen_fusion_genes <- dragen_fusion %>%
    tidyr::separate(col = FusionGene, into = c("gene1", "gene2"), sep = "--")
  
  ref_genes.list[["dragenFusion"]] <- dragen_fusion_genes
  
  runDragenFusionChunk <- TRUE
  runFusionChunk <- TRUE
}


##### Read in pizzly fusion calls
##### Check if pizzly output file exists
pizzly_file <- paste(dataDir, "pizzly", paste0(params$sample_name, "-flat.tsv"), sep = "/")
pizzly_file_filtered <- paste(dataDir, "pizzly", paste0(params$sample_name, "-flat-filtered.tsv"), sep = "/")
runPizzlyChunk <- FALSE

if ( !is.null(params$bcbio_rnaseq) &&  file.exists(pizzly_file) ) {
  ref_genes.list[["pizzly"]] <- read.table(file = pizzly_file, header = TRUE, quote = "")
  runPizzlyChunk <- TRUE
  runFusionChunk <- TRUE
} else if ( file.exists(pizzly_file_filtered) ) {
  ref_genes.list[["pizzly"]] <- read.table(file = pizzly_file_filtered, header = TRUE, quote = "")
  runPizzlyChunk <- TRUE
  runFusionChunk <- TRUE
}

##### Read in mutation data for investigate sample
##### Get the genomic output data from umccrise
if ( !is.null(params$umccrise) ) {
  umccrise <- unlist(strsplit(params$umccrise, split='/', fixed=TRUE))
  umccrise <- umccrise[length(umccrise)]
  
  ##### Check if PCGR (mutation) output file exists
  runPcgrChunk <- TRUE
  
  if ( file.exists(paste(params$umccrise, "small_variants", paste0(umccrise, "-somatic.pcgr.snvs_indels.tiers.tsv"), sep = "/")) ) {
    pcgr_file <- paste(params$umccrise, "small_variants", paste0(umccrise, "-somatic.pcgr.snvs_indels.tiers.tsv"), sep = "/")
  } else if ( file.exists(paste(params$umccrise, "..", "work", umccrise, "pcgr", paste0(umccrise, "-somatic.pcgr.snvs_indels.tiers.tsv"), sep = "/")) ) {
    pcgr_file <- paste(params$umccrise, "..", "work", umccrise, "pcgr", paste0(umccrise, "-somatic.pcgr.snvs_indels.tiers.tsv"), sep = "/")
  } else if ( file.exists(paste(params$umccrise, "pcgr", paste0(umccrise, "-somatic.pcgr.snvs_indels.tiers.tsv"), sep = "/")) ) {
    pcgr_file <- paste(params$umccrise, "pcgr", paste0(umccrise, "-somatic.pcgr.snvs_indels.tiers.tsv"), sep = "/")
  } else if ( file.exists(paste(params$umccrise, "pcgr", paste0(umccrise, "-somatic.pcgr_acmg.grch37.snvs_indels.tiers.tsv"), sep = "/")) ) {
    pcgr_file <- paste(params$umccrise, "pcgr", paste0(umccrise, "-somatic.pcgr_acmg.grch37.snvs_indels.tiers.tsv"), sep = "/")
  } else {
    runPcgrChunk <- FALSE
  }
  
  if ( runPcgrChunk ) {
    ref_genes.list[["pcgr"]] <- read.table(pcgr_file, sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, fill = TRUE, quote = "")
    
    ##### Simplify the variants types
    ref_genes.list[["pcgr"]]$CONSEQUENCE <- gsub("_variant", "", ref_genes.list[["pcgr"]]$CONSEQUENCE)
    ref_genes.list[["pcgr"]]$CONSEQUENCE <- gsub("_", " ", ref_genes.list[["pcgr"]]$CONSEQUENCE)
    
    ##### Simplify tiers' annotations and AFs
    ref_genes.list[["pcgr"]]$TIER <- gsub("TIER ", "", ref_genes.list[["pcgr"]]$TIER)
    ref_genes.list[["pcgr"]]$AF_TUMOR <- round(ref_genes.list[["pcgr"]]$AF_TUMOR, digits = 2)
  } else {
    ref_genes.list[["pcgr"]] <- NULL
  }
  
  ##### Check if purple (CN) output file exists
  purple_file_1 <- paste(params$umccrise, "purple", paste0(umccrise, ".purple.gene.cnv"), sep = "/")
  purple_file_2 <- paste(params$umccrise, "purple", paste0(umccrise, ".purple.cnv.gene.tsv"), sep = "/")
  runPurpleChunk <- TRUE
  
  if ( file.exists(purple_file_1) ) {
    ref_genes.list[["purple"]] <- read.table(purple_file_1, sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, fill = TRUE, quote = "")
  } else if ( file.exists(purple_file_2) ) {
    ref_genes.list[["purple"]] <- read.table(purple_file_2, sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, fill = TRUE, quote = "")
    colnames(ref_genes.list[["purple"]]) <- sapply(colnames(ref_genes.list[["purple"]]), CapStr)
  } else {
    ref_genes.list[["purple"]] <- NULL
    runPurpleChunk <- FALSE
  }
  
  ##### Check if manta (structural variants (SVs)) file exists
  sv_file_1 <- paste(params$umccrise, "structural", paste0(umccrise, "-sv-prioritize-manta-pass.tsv"), sep = "/")
  sv_file_2 <- paste(params$umccrise, "structural", paste0(umccrise, "-manta.tsv"), sep = "/")
  runSVsChunk <- TRUE
  
  if ( file.exists(sv_file_1) ) {
    ref_genes.list[["manta"]] <- sv_prioritize_short(sv_file_1)
  } else if ( file.exists(sv_file_2) ) {
    ref_genes.list[["manta"]] <- sv_prioritize(sv_file_2)
    ref_genes.list[["manta"]] <- ref_genes.list[["manta"]][, c("Tier", "Event", "Gene", "Effect", "Detail", "Location", "AF", "CN chg", "SR", "PR", "CN", "Ploidy", "Transcript", "Other effects")]
    
    ##### Check if there are any SVs
    if ( !is.null(ref_genes.list[["manta"]]) ) {
      
      ##### Omit SVs without assigned gene
      ref_genes.list[["manta"]] <- ref_genes.list[["manta"]][ ref_genes.list[["manta"]]$Gene != "",  ]
    } else {
      ##### Create empty dataframe
      ref_genes.list[["manta"]] <- data.frame(matrix(ncol = 14, nrow = 0))
      colnames(ref_genes.list[["manta"]]) <- c("Tier", "Event", "Gene", "Effect", "Detail", "Location", "AF", "CN chg", "SR", "PR", "CN", "Ploidy", "Transcript", "Other effects")
    }
    
  } else {
    ref_genes.list[["manta"]] <- NULL
    runSVsChunk <- FALSE
  }
  
  ##### Extract subject ID (part of the umccrise output folder name) and add it to the MySQL insert command. This will overwrite argument passed to "--clinical_id" flag
  subjectID <- unlist(strsplit(tail(unlist(strsplit(params$umccrise, split='/', fixed=TRUE)), n=1), split='__', fixed=TRUE))[1]
  
} else {
  runPcgrChunk <- FALSE
  runPurpleChunk <- FALSE
  runSVsChunk <- FALSE
}

##### Read in OncoKB (http://oncokb.org) annotations
caner_genes_annot.list[["oncokb_clin_vars"]] <- read.table(paste(params$ref_data_dir, params$oncokb_clin_vars, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="")
caner_genes_annot.list[["oncokb_all_vars"]] <- read.table(paste(params$ref_data_dir, params$oncokb_all_vars, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="", fill = TRUE)

##### Read in CIViC (https://civicdb.org/) annotations
caner_genes_annot.list[["civic_var_summaries"]] <- read.table(paste(params$ref_data_dir, params$civic_var_summaries, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="", fill = TRUE)
caner_genes_annot.list[["civic_clin_evid"]] <- read.table(paste(params$ref_data_dir, params$civic_clin_evid, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="", fill = TRUE)

##### Read in Cancer Biomarkers database (https://www.cancergenomeinterpreter.org/biomarkers) annotations. This is mainly used to annotate reported fusion events
caner_genes_annot.list[["cancer_biomarkers_trans"]] <- read.table(paste(params$ref_data_dir, params$cancer_biomarkers_trans, sep="/"), sep="\t", as.is=TRUE, header=TRUE, row.names=NULL, quote="", fill = TRUE)

##### Read in FusionGDB database (https://ccsm.uth.edu/FusionGDB/) used to annotate reported fusion events, with info about head and tail genes.
caner_genes_annot.list[["FusionGDB"]] <- read.table(paste(params$ref_data_dir, params$FusionGDB, sep="/"), sep="\t", as.is=TRUE, header=FALSE, row.names=NULL, quote="", fill = TRUE)
names(caner_genes_annot.list[["FusionGDB"]]) <- c("Hgene", "HgeneID", "Tgene", "TgeneID", "FGname", "FGID")


##### Add refenence cohort name to the sample name
if ( params$dataset_name_incl != "" ) {
  sample_name <- paste0(params$sample_name, "_", params$dataset)
} else {
  sample_name <- params$sample_name
}

##### Read in reference datasets and merge them with sample data. This part outputs a vector with first element containing the merged data and second element containing merged targets info
ref_dataset.list[[dataset]] <- combineDatasets(sample_name=sample_name, sample_counts=counts, ref_data=ref_dataset, report_dir = results_dir, dataset = dataset)
names(ref_dataset.list[[dataset]]) <- c("combined_data", "sample_annot")

##### Define internal, external and addition cancer group names based on the targets definition
int_cancer_group <- ref_dataset$int_ref[3]
ext_cancer_group <- ref_dataset$ext_ref[3]

if ( length(unique(ref_dataset.list[[dataset]][["sample_annot"]]$Target)) > 3 ) {
  
  add_cancer_group <- unique(ref_dataset.list[[dataset]][["sample_annot"]]$Target)[2]
} else {
  add_cancer_group <- NULL
}

##### Define the cancer group to be used to compare per-gene expression values and report in the summary tables
if ( dataset == "PAAD" || dataset == "PAAD-IPMN" || dataset == "PAAD-NET" || dataset == "PAAD-ACC" ) {
  comp_cancer_group <- int_cancer_group
} else {
  comp_cancer_group <- ext_cancer_group
}

##### Clean the space
rm(counts, tx2ensembl)
##### Initiate MySQL command to populate RNA-seq data portal
mysql_populate <- paste0("### MySQL command to insert data for sample \"", sample_name, "\"\nuse piedb;\nINSERT INTO RNAseq_reports ( ID ,Platform, PatientID, SampleID, Cancer, Source, Project, Report, PMID, Analysis, Summary, Date ) VALUES ( 1000000, \"RNA_seq\"")
mysql_populate_update <- "ON DUPLICATE KEY UPDATE ID=1000000 ,Platform=\"RNA_seq\""

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ", \"", subjectID, "\", \"", sample_name, "\", \"", params$dataset , "\", \"", params$sample_source , "\", \"", params$project , "\", \"", paste0(sample_name, ".RNAseq_report.html"), "\", \"", sample_name, "\", \""  )
mysql_populate_update <-  paste0(mysql_populate_update, ", PatientID=\"", subjectID, "\", SampleID=\"", sample_name, "\", Cancer=\"", params$dataset , "\", Source=\"", params$sample_source , "\", Project=\"", params$project , "\", Report=\"", paste0(sample_name, ".RNAseq_report.html"),"\", PMID=\"", sample_name, "\", Analysis=\""  )
##### Prapare data for the treatment timeline plot
##### Search for row with clinical info for investigated patient
if ( !is.na(params$clinical_id) ) {
  sampleID.col <- grep(params$clinical_id, ref_dataset.list[[dataset]][["clinical_info"]])
} else if ( !is.na(params$subject_id) ) {
  sampleID.col <- grep(params$subject_id, ref_dataset.list[[dataset]][["clinical_info"]])
} else if ( !is.na(subjectID) ) {
  sampleID.col <- grep(subjectID, ref_dataset.list[[dataset]][["clinical_info"]])
}

runClinicalChunk <- FALSE

if ( length(sampleID.col) > 0 ) {
  
  ##### Identify column and row with patients details
  if ( !is.na(params$clinical_id) ) {
    sampleID.row <- grep(params$clinical_id, ref_dataset.list[[dataset]][["clinical_info"]][, sampleID.col])
  } else if ( !is.na(params$subject_id) ) {
    sampleID.row <- grep(params$subject_id, ref_dataset.list[[dataset]][["clinical_info"]][, sampleID.col])
  } else if ( !is.na(subjectID) ) {
    sampleID.row <- grep(subjectID, ref_dataset.list[[dataset]][["clinical_info"]][, sampleID.col])
  }
  
  clinical_info <- ref_dataset.list[[dataset]][["clinical_info"]][ sampleID.row, ]
  
  ##### Prepare data frame structure for plotting
  ##### Define treatment types
  treamtent.types <- make.names(c("NEOADJUVANT REGIMEN", "ADJUVANT REGIMEN", "FIRST LINE REGIMEN", "SECOND LINE REGIMEN", "THIRD LINE REGIMEN"))
  treamtent.types_simple <- c("Neoadjuvant", "Adjuvant", "1st line", "2nd line", "3rd line")
  treamtent.df <- data.frame(matrix(ncol = 4, nrow = 0))
  colnames(treamtent.df) <- c("Treatment", "Type", "Start", "End")

  for ( i in 1:length(treamtent.types) ) {
    
    ##### Identify treatment column number
    treamtent.types.col <- grep(paste0("^",treamtent.types[i], "$"), names(clinical_info))
    
    ##### Check how many treatments of particular type were used
    treamtent.types.details <- unlist(strsplit(clinical_info[, treamtent.types.col], split=',', fixed=TRUE))
    
    ##### Add start and end info for each treatment
    if ( any(!is.na(treamtent.types.details ), na.rm = FALSE) ) {
      for ( treatment in treamtent.types.details ) {
        
        treamtent.start <- clinical_info[, treamtent.types.col+1]
        treamtent.end <- clinical_info[, treamtent.types.col+2]

        ##### Use current data if treatment is still ongoing
        today <- as.character(Sys.Date())
        treamtent.end[ is.na(treamtent.end) ] <- today
        treamtent.tmp <- data.frame( treatment, treatment, treamtent.types_simple[i], treamtent.start, treamtent.end)
        treamtent.df <- rbind( treamtent.df, treamtent.tmp)
      }
    }
  }
  
  if ( nrow(treamtent.df) > 0 ) {
    ##### For security reasons (wrt plots that go to PIEdb), change the dates but preserve the duration of the treatments
    ##### Get the earliest treatment date and set it as day 0. Then, create fake start and end dates based on the treatment length
    day0 <- sort(treamtent.df$treamtent.start, decreasing = FALSE)[1]
    treamtents.length <- treamtent.df$treamtent.end - treamtent.df$treamtent.start
    treamtents.reset <- as.Date("2000-01-01") - day0
    treamtent.df$treamtent.start <- treamtent.df$treamtent.start + treamtents.reset
    treamtent.df$treamtent.end <- treamtent.df$treamtent.start + treamtents.length
    names(treamtent.df) <- c("Treatment", "Drug", "Type", "Start",  "End")
    
    ##### Create directory for timeline plot
    PlotsDir <- paste(results_dir, "clinical_info", sep = "/")
    if ( !file.exists(PlotsDir) ) {
      dir.create(PlotsDir, recursive=TRUE)
    }
        
    ##### Record the timeline plot. NOTE, the modified dates are used here
    treatment_timeline <- lares::plot_timeline(event = treamtent.df$Treatment, start = treamtent.df$Start, end = treamtent.df$End, label = NA, group = treamtent.df$Type, title = "", subtitle = "", save = FALSE)
    
    ##### Save the plot into png file. NOTE, the modified dates are used here. As default, the plot is saved as "cv_timeline"
    lares::plot_timeline(event = treamtent.df$Treatment, start = treamtent.df$Start, end = treamtent.df$End, label = NA, group = treamtent.df$Type, title = "", subtitle = "", save = TRUE, subdir = "clinical_info")
    
    #### Clear plots to free up some memory
    if(!is.null(dev.list())) invisible(dev.off())
    
    cv_timeline.png <- readPNG("clinical_info/cv_timeline.png", native = FALSE, info = FALSE)
    
    ##### Change the size of the timeline png plot and save it as "treatment_timeline.png"
    png::writePNG(cv_timeline.png, paste(PlotsDir, "treatment_timeline.png", sep="/"), dpi=300)
    #png(paste(PlotsDir, "treatment_timeline.png", sep="/"), width = 900, height = 600, pointsize = 0.0001, res=300)
    #plot(cv_timeline.png)
    #invisible(dev.off())
    
    #### Clear plots to free up some memory
    if(!is.null(dev.list())) invisible(dev.off())
    
    ##### Remove the original plot folder
    system("rm -rf clinical_info", ignore.stdout = TRUE, ignore.stderr = TRUE)
    
    runClinicalChunk <- TRUE
  }

##### Clean the space
rm(list = ls(pattern='^treamtent.*'))
rm(clinical_info, cv_timeline.png)
}
##### Combine UMCCR cancer gene list (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv) with OncoKB cancer genes
genes_cancer <- ref_genes.list[["genes_oncokb"]]
genes_cancer$UMCCR <- rep("No", nrow(genes_cancer))
genes_cancer$Oncogene <- rep("-", nrow(genes_cancer))
genes_cancer$TSG <- rep("-", nrow(genes_cancer))
genes_cancer$Fusion <- rep("-", nrow(genes_cancer))
genes_cancer$Germline <- rep("-", nrow(genes_cancer))

##### Flag Oncogenes, TSGs and fusion genes in the UMCCR cancer genes list (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
ref_genes.list[["genes_cancer"]]$germ <- gsub("TRUE", "Yes", ref_genes.list[["genes_cancer"]]$germ)
ref_genes.list[["genes_cancer"]]$germ <- gsub("FALSE", "-", ref_genes.list[["genes_cancer"]]$germ)
ref_genes.list[["genes_cancer"]]$fusion <- gsub("TRUE", "Yes", ref_genes.list[["genes_cancer"]]$fusion)
ref_genes.list[["genes_cancer"]]$fusion <- gsub("FALSE", "-", ref_genes.list[["genes_cancer"]]$fusion)
ref_genes.list[["genes_cancer"]]$tumorsuppressor <- gsub("TRUE", "Yes", ref_genes.list[["genes_cancer"]]$tumorsuppressor)
ref_genes.list[["genes_cancer"]]$tumorsuppressor <- gsub("FALSE", "-", ref_genes.list[["genes_cancer"]]$tumorsuppressor)
ref_genes.list[["genes_cancer"]]$oncogene <- gsub("TRUE", "Yes", ref_genes.list[["genes_cancer"]]$oncogene)
ref_genes.list[["genes_cancer"]]$oncogene <- gsub("FALSE", "-", ref_genes.list[["genes_cancer"]]$oncogene)

for ( gene in unlist(ref_genes.list[["genes_cancer"]]$symbol ) ) {
  ##### Check if the UMCCR genes is already reported in OncoKB
  if ( gene %in% genes_cancer$Hugo.Symbol ) {
   
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$UMCCR <- "Yes"
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$Oncogene <- ref_genes.list[["genes_cancer"]]$oncogene[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$TSG <- ref_genes.list[["genes_cancer"]]$tumorsuppressor[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$Fusion <- ref_genes.list[["genes_cancer"]]$fusion[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$Germline <- ref_genes.list[["genes_cancer"]]$germ[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, 2] <- as.numeric(genes_cancer[ genes_cancer$Hugo.Symbol==gene, 2]) + 1
    
  ##### Add if not present
  } else {
    genes_cancer <- rbind(genes_cancer, c(gene, 1, "No", rep("", 8), "Yes"))
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$Oncogene <- ref_genes.list[["genes_cancer"]]$oncogene[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$TSG <- ref_genes.list[["genes_cancer"]]$tumorsuppressor[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$Fusion <- ref_genes.list[["genes_cancer"]]$fusion[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
    genes_cancer[ genes_cancer$Hugo.Symbol==gene, ]$Germline <- ref_genes.list[["genes_cancer"]]$germ[ref_genes.list[[ "genes_cancer"]]$symbol==gene]
  }
}

##### Make the data frame to look nicer
rownames(genes_cancer) <- genes_cancer$Hugo.Symbol
names(genes_cancer) <- c("Gene", "Gene panels no.", "OncoKB", "Oncogene (OncoKB)", "TSG (OncoKB)", "MSK-IMPACT", "MSK-HEME", "Foundation One", "Foundation One Heme", "Vogelstein", "Sanger CGC", "UMCCR", "Oncogene", "TSG", "Fusion", "Germline")
genes_cancer <- genes_cancer[,c("Oncogene", "TSG", "Fusion", "Germline", "Gene panels no.", "UMCCR", "OncoKB", "MSK-IMPACT", "MSK-HEME", "Foundation One", "Foundation One Heme", "Vogelstein", "Sanger CGC")]
genes_cancer[ genes_cancer=="No" ] <- "-"
genes_cancer[ genes_cancer=="" ] <- "-"

ref_genes.list[["genes_cancer"]] <- genes_cancer
ref_genes.list[["genes_oncokb"]] <- genes_cancer[ rownames(genes_cancer) %in% ref_genes.list[["genes_oncokb"]]$Hugo.Symbol, ]

##### Clean the space
rm(genes_cancer)
##### Record all genes of interest to make sure that these are not filtered out during read counts data processing
# PCGR annotation of mutated genes in given patient based on PCGR report, including only those with variants classified according to user-defined tier
if ( runPcgrChunk ) {
  ref_genes.list[["summary"]]$Mutated <- unique(ref_genes.list[["pcgr"]][ ref_genes.list[["pcgr"]]$TIER %in% c(1:params$pcgr_tier), ]$SYMBOL)
  
  ##### Include splice region variants
  if ( params$pcgr_splice_vars ) {
    ref_genes.list[["summary"]]$Mutated <- unique( c(ref_genes.list[["summary"]]$Mutated,  ref_genes.list[["pcgr"]][ grepl("NONCODING.*splice region", paste0(ref_genes.list[["pcgr"]]$TIER, ".", ref_genes.list[["pcgr"]]$CONSEQUENCE), fixed = FALSE), ]$SYMBOL) )
  }
  
  ##### Remove NAs
  if ( length(ref_genes.list[["summary"]]$Mutated) > 0 ) {
    ref_genes.list[["summary"]]$Mutated <- ref_genes.list[["summary"]]$Mutated[ !(is.na(ref_genes.list[["summary"]]$Mutated)) ]
  } else {
    ref_genes.list[["summary"]]$Mutated <- NULL
  }
}
    
# ARRIBA and PIZZLY annotation of gene fusion events detected in given patient based on PIZZLY results
if ( runFusionChunk ) {
  
  if ( runArribaChunk ) {
    ref_genes.list[["summary"]]$Fusion <- unique(c(as.character(ref_genes.list[["arriba"]]$X.gene1), as.character(ref_genes.list[["arriba"]]$gene2)))
  } else {
    ref_genes.list[["summary"]]$Fusion <- NULL
  }
  
  if ( runPizzlyChunk ) {
    ref_genes.list[["summary"]]$Fusion <- unique(c(ref_genes.list[["summary"]]$Fusion, as.character(ref_genes.list[["pizzly"]]$geneA.name), as.character(ref_genes.list[["pizzly"]]$geneB.name)))
  }
  
  if ( runDragenFusionChunk ) {
    ref_genes.list[["summary"]]$Fusion <- unique(c(ref_genes.list[["summary"]]$Fusion, as.character(ref_genes.list[["dragenFusion"]]$gene1), as.character(ref_genes.list[["dragenFusion"]]$gene2)))
  }
  
  ##### Remove NAs
  if ( length(ref_genes.list[["summary"]]$Mutated) > 0 ) {
    ref_genes.list[["summary"]]$Fusion <- ref_genes.list[["summary"]]$Fusion[ !(is.na(ref_genes.list[["summary"]]$Fusion)) ]
  } else {
    ref_genes.list[["summary"]]$Fusion <- NULL
  }
}

# MANTA annotation of structural variants (SVs) with affected genes in given patient based on MANTA results
if ( runSVsChunk ) {
  ref_genes.list[["summary"]]$SV <- ref_genes.list[["manta"]]
  ref_genes.list[["summary"]]$SV <- ref_genes.list[["summary"]]$SV[ ref_genes.list[["summary"]]$SV$Gene != "",  ]$Gene
  # ...and distinguish classified by MANTA as fusion genes
  
  ##### Remove NAs
  if ( length(ref_genes.list[["summary"]]$SV) > 0 ) {
    ref_genes.list[["summary"]]$SV <- unique(unlist(strsplit(ref_genes.list[["summary"]]$SV, split='&', fixed=TRUE)))
    ref_genes.list[["summary"]]$SV <- ref_genes.list[["summary"]]$SV[ !(is.na(ref_genes.list[["summary"]]$SV)) ]
  } else {
    ref_genes.list[["summary"]]$SV <- NULL
  }
}

# PURPLE annotation of copy-number (CN) altered genes in given patient based on PURPLE results, including only those with CN values meeting user-defined thresholds
if ( runPurpleChunk ) {
  ref_genes.list[["summary"]]$CN <- ref_genes.list[["purple"]]
  ref_genes.list[["summary"]]$CN <- ref_genes.list[["summary"]]$CN[ ref_genes.list[["summary"]]$CN %!in% "",  ]
  
  ##### Get the CN mean
  ref_genes.list[["summary"]]$CN$MeanCopyNumber <- rowMeans(cbind(ref_genes.list[["summary"]]$CN$MinCopyNumber, ref_genes.list[["summary"]]$CN$MaxCopyNumber))
    
  ##### Deal with negative CN values
  ref_genes.list[["summary"]]$CN$MeanCopyNumber[ ref_genes.list[["summary"]]$CN$MeanCopyNumber < 0 ] <- 0

  ##### Limit the data to include only cancer genes
  ref_genes.list[["summary"]]$CN <- ref_genes.list[["summary"]]$CN[ ref_genes.list[["summary"]]$CN$Gene %in% rownames(ref_genes.list[["genes_cancer"]]), ]

  ##### Keep only altered genes with CN values below loss threshold (default 5th percentile) and above gain threshold (default 95th percentile)
  if ( params$cn_loss == 5 && params$cn_gain == 95 ) {
    cn_data.all.percent <- quantile(ref_genes.list[["summary"]]$CN$MeanCopyNumber, probs = seq(0, 1, .05), na.rm = TRUE)
    cn_bottom <- round(cn_data.all.percent[2], digits = 2)
    cn_top <- round(cn_data.all.percent[20], digits = 2)
  
  } else {
    cn_bottom <- params$cn_loss
    cn_top <- params$cn_gain
  }
  
  ##### If the difference is 0 then increase/decrease threshold by 1
  if  ( abs(cn_top-cn_bottom) == 0 ) {
    cn_top <- cn_top + 1
    cn_bottom <- cn_bottom - 1
  }
  
  ref_genes.list[["summary"]]$CN <- unique(ref_genes.list[["summary"]]$CN[ ref_genes.list[["summary"]]$CN$MeanCopyNumber <= cn_bottom | ref_genes.list[["summary"]]$CN$MeanCopyNumber >= cn_top, ]$Gene)
  
  ##### Remove NAs
  if ( length(ref_genes.list[["summary"]]$CN) > 0 ) {
    ref_genes.list[["summary"]]$CN <- ref_genes.list[["summary"]]$CN[ !(is.na(ref_genes.list[["summary"]]$CN)) ]
  } else {
    ref_genes.list[["summary"]]$CN <- NULL
  }
}

# Immune reponse markers
ref_genes.list[["summary"]]$Immune <- unique(ref_genes.list[["genes_immune"]]$immune_markers$SYMBOL)

if ( params$immunogram ) {
  ref_genes.list[["summary"]]$Immune <- unique(c(ref_genes.list[["summary"]]$Immune, ref_genes.list[["genes_immune"]]$immunogram$SYMBOL))
  
  ##### Remove NAs
  ref_genes.list[["summary"]]$Immune <- ref_genes.list[["summary"]]$Immune[ !(is.na(ref_genes.list[["summary"]]$Immune)) ]
}

# HRD (homologous recombination deficiency) genes
ref_genes.list[["summary"]]$HRD <- unique(ref_genes.list[["genes_hrd"]]$SYMBOL)

##### Remove NAs
ref_genes.list[["summary"]]$HRD <- ref_genes.list[["summary"]]$HRD[ !(is.na(ref_genes.list[["summary"]]$HRD)) ]
  
# Cancer genes derived from UMCCR Cancer Gene list (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv) and from OncoKB portal (http://oncokb.org/#/cancerGenes)
ref_genes.list[["summary"]]$Cancer <- rownames(ref_genes.list[["genes_cancer"]])

##### Remove NAs
ref_genes.list[["summary"]]$Cancer <- ref_genes.list[["summary"]]$Cancer[ !(is.na(ref_genes.list[["summary"]]$Cancer)) ]

##### Record all genes of interest
genes2keep <- unique( unlist(ref_genes.list[["summary"]]) )
##### Get gene symbols for the genes of interest. These genes will not be filtered out due to low/insufficient expression
##### Get genes annotation and genomic locations
edb <- eval(parse(text = paste0("EnsDb.Hsapiens.v", params$ensembl_version)))
  
##### Get keytypes for gene SYMBOL
keys <- keys(edb, keytype="GENEID")
  
##### Get genes genomic coordiantes
gene_info <- ensembldb::select(edb, keys=keys, columns=c("GENEID", "GENENAME"), keytype="GENEID")
names(gene_info) <- gsub("GENEID", "ENSEMBL", names(gene_info))
names(gene_info) <- gsub("GENENAME", "SYMBOL", names(gene_info))
  
##### Limit genes annotation to the gene of interest
genes2keep <- gene_info[ gene_info$SYMBOL %in% genes2keep,  ]
  
##### Remove rows with duplicated ENSEMBL IDs
genes2keep = genes2keep[!duplicated(genes2keep$ENSEMBL),]
rownames(genes2keep) <- genes2keep$ENSEMBL

##### Remove rows with duplicated gene symbols (Y_RNAs, SNORs, LINC0s etc). Preferably select ENSEMBL ID that is used in the count data
genes2keep.combined_data <- genes2keep[ genes2keep$ENSEMBL %in% rownames(ref_dataset.list[[dataset]]$combined_data), ]
genes2keep <- genes2keep[ genes2keep$SYMBOL %!in% genes2keep.combined_data$SYMBOL, ]
genes2keep <-  genes2keep[!duplicated(genes2keep$SYMBOL),]
genes2keep <- rbind(genes2keep.combined_data, genes2keep)

##### Add column to store info about filtered genes
genes2keep$EXP <- TRUE

##### Clean the space
rm(edb, keys, gene_info)
suppressMessages(library(plotly))
##### Generate bar-plot for library size. The colours indicate sample groups, as provided in *Target* column in the sample annotation file

data <- ref_dataset.list[[dataset]][["combined_data"]]
target <- ref_dataset.list[[dataset]][["sample_annot"]]
target$Target[ target$Target==sample_name ] <- "Patient"
rownames(target)[ rownames(target)==sample_name ] <- "Patient"

##### Change the datasets levels order
target$Target <- factor(target$Target, levels = unique(target$Target))

##### Assigne colours to targets and datasets
targets.colour <- getColours(target$Target)

##### Prepare data frame
data.df <- data.frame(rownames(target), as.numeric(colSums(data)*1e-6), target$Target)
colnames(data.df) <- c("Sample", "Library_size", "Target")

##### The default order will be alphabetized unless specified as below
data.df$Sample <- factor(data.df$Sample, levels = data.df[["Sample"]])

library_size <- plot_ly(data.df, x = ~Sample, y = ~Library_size, color = ~Target, colors = targets.colour[[1]], type = 'bar', width = 800, height = 400) %>%
  layout(title = "", xaxis = list( tickfont = list(size = 10), title = "", showticklabels = FALSE), yaxis = list(title = "Library size (millions)"), margin = list(l=50, r=50, b=50, t=50, pad=4), autosize = F, showlegend=TRUE, legend = list(orientation = 'h', y = max(data.df$Library_size), bgcolor = "white"))

##### Create directory for input data plots
PlotsDir <- paste(results_dir, "InputDataPlots", sep = "/")
if ( !file.exists(PlotsDir) ) {
  dir.create(PlotsDir, recursive=TRUE)
}

##### Save interactive plot as html file
saveWidgetFix(library_size, file = paste(PlotsDir, "library_size.html", sep = "/"))
  
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)
##### Filtering to remove low expressed genes. For differential expression and related analyses, gene expression is rarely considered at the level of raw counts since libraries sequenced at a greater depth will result in higher counts. Rather, it is common practice to transform raw counts onto a scale that accounts for such library size differences. Genes with very low counts across all libraries provide little evidence for differential expression. In the biological point of view, a gene must be expressed at some minimal level before it is likely to be translated into a protein or to be biologically important. In addition, the pronounced discretenes of these counts interferes with some of the statistical approximations that are used later in the pipeline. These genes should be filtered out prior to further analysis. Users should filter with CPM rather than filtering on the counts directly, as the latter does not account for differences in library sizes between samples. For instance for the CPM-transformed data we keep only genes that have CPM of 1

##### Transformation to CPM or TPM scale (see these blogs for details https://www.rna-seqblog.com/rpkm-fpkm-and-tpm-clearly-explained/ and https://haroldpimentel.wordpress.com/2014/05/08/what-the-fpkm-a-review-rna-seq-expression-units/ ).  CPM = Counts Per Million,  TPM = Transcripts Per Kilobase Million. 

##### For counts data processing consider the investigated sample and internal reference cohort as one group  (regardless of the investigated patient tissie origin), and TCGA data (of any cancer type) as another group. This is to facilitate batch-effects (related with technical aspects) correction process
target_mod <- ref_dataset.list[[dataset]][["sample_annot"]]
target_mod$Dataset <- gsub(sample_name, int_cancer_group, target_mod$Dataset)
targets_mod.list <- unique(target_mod$Dataset)

##### Create lists with processed data each group
y <- vector("list", length(targets_mod.list))
names(y) <- targets_mod.list

##### Keep info about samples with the lowest and greates counts for defined CPM threshold
cpm.min <- round(min(as.numeric(colSums(ref_dataset.list[[dataset]][["combined_data"]])*1e-6)), digits=0)
cpm.max <- round(max(as.numeric(colSums(ref_dataset.list[[dataset]][["combined_data"]])*1e-6)), digits=0)

#### For each group...
for ( group in targets_mod.list ) {
    target <- target_mod[ target_mod$Dataset==group, ]
    data <- ref_dataset.list[[dataset]][["combined_data"]]
    data <- data[ , target_mod$Dataset==group]
    
  ##### CPM transformation and filtering
  if ( params$filter && params$transform == "CPM" ) {
    
    ##### Create EdgeR DGEList object
    y[[group]] <- edgeR::DGEList(counts=data,  group=target$Dataset)
    
    ##### Keep genes with CPM of at least 1 in more than 10% of samples
    filter.threshold <- 1
    keep <- rowSums(edgeR::cpm(y[[group]])>filter.threshold) >= ncol(data)/10
    
    ##### Note which genes of interest are not expressed
    genes2keep$EXP[ rownames(genes2keep) %!in% names(keep) ] <- FALSE
    
    ##### Keep the genes of interest too
    keep[ names(keep) %in% rownames(genes2keep) ] <- TRUE
    y[[group]]$filtered <- y[[group]][keep, , keep.lib.sizes=FALSE]
    
    ##### Transform the raw-scale to CPM. Add small offset to each observation to avoid taking log of zero
    y[[group]]$transformed <- edgeR::cpm(y[[group]], normalized.lib.sizes=FALSE, log=params$log, prior.count=0.25)
    y[[group]]$filtered.transformed <- edgeR::cpm(y[[group]]$filtered, normalized.lib.sizes=FALSE, log=params$log, prior.count=0.25)
  
  ##### CPM transformation without filtering
  } else if ( !params$filter && params$transform == "CPM" ) {
    ##### Create EdgeR DGEList object
    y[[group]] <- edgeR::DGEList(counts=data,  group=target$Dataset)
    
    ##### Transform the raw-scale to CPM. Add small offset to each observation to avoid taking log of zero
    y[[group]]$transformed <- edgeR::cpm(y[[group]], normalized.lib.sizes=FALSE, log=params$log, prior.count=0.25)
    
  ##### TPM data transformation. We can convert RPKM to TPM in two different ways: from pre-calculated RPKM, by diving by the sum of RPKM values, or directly from the normalized counts. Here we calculate TPM starting from RPKM values computed using edgeR's rpkm function ( from http://luisvalesilva.com/datasimple/rna-seq_units.html )
  ##### TPM transformation with filtering
  } else if ( params$filter && params$transform == "TPM" ) {
    
    ##### Get genes lengths
    edb <- eval(parse(text = paste0("EnsDb.Hsapiens.v", params$ensembl_version)))
    gene.length <- lengthOf(edb, filter = GeneIdFilter(rownames(data)))
    
    ##### Check for which genes the lenght info is not available and remove them from the data
    genes.no_length <- rownames(data)[ rownames(data) %!in% names(gene.length)]
    data <- data[ rownames(data) %!in% genes.no_length, ]
    
    ##### Create EdgeR DGEList object
    y[[group]] <- edgeR::DGEList(counts=data,  group=target$Dataset)
    
    ##### Convert data into RPKM
    y[[group]]$transformed <- edgeR::rpkm(y[[group]], gene.length = gene.length, normalized.lib.sizes=FALSE, log=FALSE)
    
    ##### ... and then to TPM scale. Add small offset to each observation to avoid taking log of zero
    if ( params$log ) {
      y[[group]]$transformed <- log2(tpm_from_rpkm(y[[group]]$transformed+0.25))
      
      ##### Keep genes with TPM of at least 1 in more than 10% of samples
      filter.threshold <- 1+0.25
      keep <- rowSums(y[[group]]$transformed > filter.threshold) >= ncol(y[[group]]$transformed)/10
      
      ##### Note which genes of interest are not expressed
      genes2keep$EXP[ rownames(genes2keep) %!in% names(keep) ] <- FALSE
    
      ##### Keep the genes of interest too
      keep[ names(keep) %in% rownames(genes2keep) ] <- TRUE
      y[[group]]$filtered <- y[[group]]$counts[keep, ]
      y[[group]]$filtered.transformed <- y[[group]]$transformed[keep, ]
   
    } else {
      y[[group]]$transformed <- tpm_from_rpkm(y[[group]]$transformed)
      
      ##### Keep genes with TPM of at least 1 in more than 10% of samples
      filter.threshold <- 1
      keep <- rowSums(y[[group]]$transformed > filter.threshold) >= ncol(y[[group]]$transformed)/10
      
      ##### Note which genes of interest are not expressed
      genes2keep$EXP[ rownames(genes2keep) %!in% names(keep) ] <- FALSE
    
      ##### Keep the genes of interest too
      keep[ names(keep) %in% rownames(genes2keep) ] <- TRUE
      y[[group]]$filtered <- y[[group]]$counts[keep, ]
      y[[group]]$filtered.transformed <- y[[group]]$transformed[keep, ]
    }
  
  ##### TPM transformation without filtering
  } else if ( !params$filter && params$transform == "TPM" ) {
    
    ##### Get genes lengths
    edb <- eval(parse(text = paste0("EnsDb.Hsapiens.v", params$ensembl_version)))
    gene.length <- lengthOf(edb, filter = GeneIdFilter(rownames(data)))
    
    ##### Check for which genes the lenght info is not available and remove them from the data
    genes.no_length <- rownames(data)[ rownames(data) %!in% names(gene.length)]
    data <- data[ rownames(data) %!in% genes.no_length, ]
    
    ##### Create EdgeR DGEList object
    y[[group]] <- edgeR::DGEList(counts=data,  group=target$Dataset)
    
    ##### Convert data into RPKM
    y[[group]]$transformed <- edgeR::rpkm(y[[group]], gene.length = gene.length, normalized.lib.sizes=FALSE, log=FALSE)
    
    ##### ... and then to TPM scale. Add small offset to each observation to avoid taking log of zero
    if ( params$log ) {
      y[[group]]$transformed <- log2(tpm_from_rpkm(y[[group]]$transformed+0.25))
    } else {
      y[[group]]$transformed <- tpm_from_rpkm(y[[group]]$transformed)
    }
  }
}

##### Now combine DGEList objects created for each group
y[["comb"]]$transformed <- cbind(y[[targets_mod.list[1]]]$transformed, y[[targets_mod.list[2]]]$transformed)
y[["comb"]]$samples <- rbind(y[[targets_mod.list[1]]]$samples, y[[targets_mod.list[2]]]$samples)

if ( params$filter ) {
  
  ##### Keep only genes present in all sets
  genes_mod <- intersect(rownames(y[[targets_mod.list[1]]]$filtered), rownames(y[[targets_mod.list[2]]]$filtered))
  y[[targets_mod.list[1]]]$filtered <- y[[targets_mod.list[1]]]$filtered[ rownames(y[[targets_mod.list[1]]]$filtered) %in% genes_mod, ]
  y[[targets_mod.list[2]]]$filtered <- y[[targets_mod.list[2]]]$filtered[ rownames(y[[targets_mod.list[2]]]$filtered) %in% genes_mod, ]
  y[[targets_mod.list[1]]]$filtered.transformed <- y[[targets_mod.list[1]]]$filtered.transformed[ rownames(y[[targets_mod.list[1]]]$filtered.transformed) %in% genes_mod, ]
  y[[targets_mod.list[2]]]$filtered.transformed <- y[[targets_mod.list[2]]]$filtered.transformed[ rownames(y[[targets_mod.list[2]]]$filtered.transformed) %in% genes_mod, ]
 
  y[["comb"]]$filtered <- cbind(y[[targets_mod.list[1]]]$filtered, y[[targets_mod.list[2]]]$filtered)
  y[["comb"]]$filtered.transformed <- cbind(y[[targets_mod.list[1]]]$filtered.transformed, y[[targets_mod.list[2]]]$filtered.transformed)
}

##### Clean the space
rm(target, target_mod, genes_mod, keep)
##### Assign colours to targets and datasets
target <- ref_dataset.list[[dataset]][["sample_annot"]]
targets.colour <- getColours(target$Target)
  
##### Collect the most extreme density values for set the x-axis and y-axis boundaries
den.x <- density(y[["comb"]]$transformed[,1])$x
den.y <- density(y[["comb"]]$transformed[,1])$y
  
for (i in 2:ncol(y[["comb"]]$transformed)) {
  den <- density(y[["comb"]]$transformed[,i])
  den.x <- sort(c(den.x, den$x))
  den.y <- sort(c(den.y, den$y))
}

##### Plot read counts against transformed data
if ( params$filter ) {
  suppressMessages(library(plotly))
  
  ##### Organise the data into data frame
  if ( params$log ) {
    data.df <- as.data.frame(cbind( exp(y[["comb"]]$transformed[,ncol(y[["comb"]]$transformed)]), ref_dataset.list[[dataset]][["combined_data"]][,ncol(ref_dataset.list[[dataset]][["combined_data"]])]))
    names(data.df) <- c("Transformed", "Counts")
    data.df$Transformed <- log(data.df$Transformed)
    
  } else {
     data.df <- as.data.frame(cbind( y[["comb"]]$transformed[,ncol(y[["comb"]]$transformed)], ref_dataset.list[[dataset]][["combined_data"]][,ncol(ref_dataset.list[[dataset]][["combined_data"]])]))
    names(data.df) <- c("Transformed", "Counts")
  }
  
  ##### Keep only genes with read counts below the 99th percentile
  data.df <- data.df[ data.df$Counts < quantile(data.df$Counts, 0.99), ]
  
  ##### Keep only every 25th genes to reduce the size of the plot
  data.df <- data.df[ seq(1,nrow(data.df), by=25), ]
  
  ##### Generate plot for filtered data
  counts_vs_transformed <- plot_ly( data.df, x = ~Transformed, y = ~Counts, width = 800, height = 300, color = I('black'), marker = list(size = 5), type="scatter", mode = "markers", name = paste0(params$transform, " / Counts (Patient)") ) %>% 
    add_trace(x = c(filter.threshold, filter.threshold), y= c(0, max(data.df$Counts)), mode = "lines", color = I("red"), name = "Filtering threshold") %>%
    
    layout(title = "", xaxis = list(title = paste0(params$transform, "s")), yaxis = list(title = "Counts"), showlegend=TRUE)
  
  ##### Save interactive plot as html file
  saveWidgetFix(counts_vs_transformed, file = paste(PlotsDir, "counts_vs_transformed.html", sep = "/"))

  ##### Detach plotly package. Otherwise it clashes with other graphics devices
  detach("package:plotly", unload=FALSE)
  
  if ( !is.null(add_cancer_group) ) {
    legend <- c(ext_cancer_group, add_cancer_group, int_cancer_group, "Patient")
  } else {
    legend <- c(ext_cancer_group, int_cancer_group, "Patient")
  }
  
  ##### Before filtering
  par(mfrow=c(1,2))
  plot(density(y[["comb"]]$transformed[,1]), lwd=2, xlim=c(den.x[1],max(data.df$Transformed)), ylim=c(den.y[1],den.y[length(den.y)]), las=2, main="", xlab="", col=targets.colour[[2]][1])
  title(main="Transformed data (unfiltered)", xlab=params$transform)
  abline(v=0, lty=3)
  
  for (i in 2:ncol(y[["comb"]]$transformed)){
    den <- density(y[["comb"]]$transformed[,i])
    lines(den$x, den$y, lwd=2, col=targets.colour[[2]][i])
  }
  legend("topright", legend=legend, fill=targets.colour[[1]], bty="n", bg = "transparent")
  
  data_transformation_nonfiltered <- recordPlot()
  
  ##### After filtering
  plot(density(y[["comb"]]$filtered.transformed[,1]), lwd=2, xlim=c(den.x[1],max(data.df$Transformed)), ylim=c(den.y[1],den.y[length(den.y)]), las=2, main="", xlab="", col=targets.colour[[2]][1])
  title(main="Transformed and filtered data", xlab=params$transform)
  abline(v=0, lty=3)
  
  for (i in 2:ncol(y[["comb"]]$filtered.transformed)){
    den <- density(y[["comb"]]$filtered.transformed[,i])
    lines(den$x, den$y, lwd=2, col=targets.colour[[2]][i])
  }
  legend("topright", legend=legend, fill=targets.colour[[1]], bty="n", bg = "transparent")
  
  data_transformation_filtered <- recordPlot()
  
  ##### Save the plot as png file
  png(paste0(PlotsDir, "/filtering.png"), width=900, height=400, pointsize = 14)
  par(mfrow=c(1,2))
  
  ##### Before filtering
  plot(density(y[["comb"]]$transformed[,1]), lwd=2, xlim=c(den.x[1],den.x[length(den.x)]), ylim=c(den.y[1],den.y[length(den.y)]), las=2, main="", xlab="", col=targets.colour[[2]][1])
  title(main="Transformed data (unfiltered)", xlab=params$transform)
  abline(v=0, lty=3)
  
  for (i in 2:ncol(y[["comb"]]$transformed)){
    den <- density(y[["comb"]]$transformed[,i])
    lines(den$x, den$y, lwd=2, col=targets.colour[[2]][i])
  }
  legend("topright", legend=legend, fill=targets.colour[[1]], cex = 0.7, bty="n", bg = "transparent")
  
  ##### After filtering
  plot(density(y[["comb"]]$filtered.transformed[,1]), lwd=2, xlim=c(den.x[1],den.x[length(den.x)]), ylim=c(den.y[1],den.y[length(den.y)]), las=2, main="", xlab="", col=targets.colour[[2]][1])
  title(main="Transformed and filtered data", xlab=params$transform)
  abline(v=0, lty=3)
  
  for (i in 2:ncol(y[["comb"]]$filtered.transformed)){
    den <- density(y[["comb"]]$filtered.transformed[,i])
    lines(den$x, den$y, lwd=2, col=targets.colour[[2]][i])
  }
  legend("topright", legend=legend, fill=targets.colour[[1]], cex = 0.7, bty="n", bg = "transparent")
  invisible(dev.off())
  
##### Without filtering
} else {
  plot(density(y[["comb"]]$transformed[,1]), lwd=2, xlim=c(den.x[1],den.x[length(den.x)]), ylim=c(den.y[1],den.y[length(den.y)]), las=2, main="", xlab="", col=targets.colour[[2]][1])
  title(main="Transformed data (unfiltered)", xlab=params$transform)
  abline(v=0, lty=3)
  
  for (i in 2:ncol(y[["comb"]]$transformed)){
    den <- density(y[["comb"]]$transformed[,i])
    lines(den$x, den$y, lwd=2, col=targets.colour[[2]][i])
  }
  legend("topright", legend=legend, fill=targets.colour[[1]], bty="n", bg = "transparent")
  
  data_transformation_nonfiltered <- recordPlot()
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
  
  ##### Save the plot as png file
  png(paste0(PlotsDir, "/filtering.png"), width=900, height=400, pointsize = 14)
  plot(density(y[["comb"]]$transformed[,1]), lwd=2, xlim=c(den.x[1],den.x[length(den.x)]), ylim=c(den.y[1],den.y[length(den.y)]), las=2, main="", xlab="", col=targets.colour[[2]][1])
  title(main="Transformed data (unfiltered)", xlab=params$transform)
  abline(v=0, lty=3)
  
  for (i in 2:ncol(y[["comb"]]$transformed)){
    den <- density(y[["comb"]]$transformed[,i])
    lines(den$x, den$y, lwd=2, col=targets.colour[[2]][i])
  }
  legend("topright", legend=legend, fill=targets.colour[[1]], cex = 0.7, bty="n", bg = "transparent")
  invisible(dev.off())
}
##### Clean the space
rm(data, data.df, target, den.x, den.y)
##### During the sample preparation or sequencing process, external factors that are not of biological interest can affect the expression of individual samples. For example, samples processed in the first batch of an experiment can have higher expression overall when compared to samples processed in a second batch. It is assumed that all samples should have a similar range and distribution of expression values. Normalisation for sample-specific effects is required to ensure that the expression distributions of each sample are similar across the entire experiment.

##### TMM normalsation. Trimmed mean of M-values (https://www.ncbi.nlm.nih.gov/pubmed/20196867) (TMM) is performed using the calcNormFactors function in edgeR. The normalisation factors calculated here are used as a scaling factor for the library sizes. TMM is the recommended for most RNA-Seq data where the majority (more than half) of the genes are believed not differentially expressed between any pair of the samples. It adjusts for RNA composition effect, calculates scaling factors for the library sizes with calcNormFactors function using trimmed mean of M-values (TMM) between each pair of samples. Note, that the raw read counts are used to calculate the normalisation factors
  
#### For each group...
for ( group in targets_mod.list ) {
  if ( params$transform == "CPM" ) {
    
    ##### Calculate normalization factors and transformations from the raw-scale to CPM and normalisation using user-defined method
    if ( params$filter ) {
      y[[group]]$noNorm <- y[[group]]$filtered.transformed
      y[[group]]$filtered$samples["norm.factors"] <- edgeR::calcNormFactors(y[[group]]$filtered, method = params$norm)$samples["norm.factors"]
      y[[group]]$norm <- edgeR::cpm(y[[group]]$filtered, normalized.lib.sizes=TRUE, log=params$log, prior.count=0.25)
    
    } else {
      y[[group]]$noNorm <- y[[group]]$transformed
      y[[group]]$samples["norm.factors"] <- edgeR::calcNormFactors(y[[group]], method = params$norm)$samples["norm.factors"]
      y[[group]]$norm <- edgeR::cpm(y[[group]], normalized.lib.sizes=TRUE, log=params$log, prior.count=0.25)
    }
    
  ##### Quantile normalsation (from https://www.biostars.org/p/296992/ )
  } else if ( params$transform == "TPM" ) {
    
    ##### Normalisation using quantile method
    if ( params$filter ) {
      y[[group]]$noNorm <- y[[group]]$filtered.transformed
      y[[group]]$filtered.transformed <- data.matrix(y[[group]]$filtered.transformed) 
      
      if ( tolower(params$norm) != "none" ) {
        y[[group]]$norm  <- normalize.quantiles(y[[group]]$filtered.transformed, copy = TRUE)
        colnames(y[[group]]$norm) <- colnames(y[[group]]$filtered.transformed)
        rownames(y[[group]]$norm) <- rownames(y[[group]]$filtered.transformed)
      } else {
        y[[group]]$norm  <- y[[group]]$filtered.transformed
      }
    } else {
      y[[group]]$noNorm <- y[[group]]$transformed
      y[[group]]$transformed <- data.matrix(y[[group]]$transformed)
      
      if ( tolower(params$norm) != "none" ) {
        y[[group]]$norm  <- normalize.quantiles(y[[group]]$transformed, copy = TRUE)
        colnames(y[[group]]$norm) <- colnames(y[[group]]$transformed)
        rownames(y[[group]]$norm) <- rownames(y[[group]]$transformed)
      } else {
        y[[group]]$norm  <- y[[group]]$transformed
      }
    }
  }
}  

##### Combine DGEList objects created for each group
y[["comb"]]$noNorm <- cbind(y[[targets_mod.list[1]]]$noNorm, y[[targets_mod.list[2]]]$noNorm)
y[["comb"]]$norm <- cbind(y[[targets_mod.list[1]]]$norm, y[[targets_mod.list[2]]]$norm)

if ( tolower(params$norm) != "none" ) {
  ref_dataset.list[[dataset]][["combined_data_processed"]] <- y[["comb"]]$norm
} else {
  ref_dataset.list[[dataset]][["combined_data_processed"]] <- y[["comb"]]$noNorm
}

##### Clean the space
rm(targets_mod.list)
##### Plot expression distribution of samples for unnormalised and normalised data
par(mfrow=c(2,1), mar=c(2, 5, 3, 2))

##### Unnormalised data
boxplot(y[["comb"]]$noNorm, las=2, col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
title(main="Unnormalised data", ylab=params$transform)
legend("topright", legend=legend, fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")

data_nonnormalised <- recordPlot()

##### Normalised data
boxplot(y[["comb"]]$norm, las=2, col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
title(main=paste0("Normalised data (", params$norm, ")"), ylab=params$transform)
legend("topright", legend=legend, fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
data_normalised <- recordPlot()

##### Save the plot as png file
png(paste0(PlotsDir, "/normalisation.png"), width=900, height=700, pointsize = 14)
par(mfrow=c(2,1), mar=c(2, 5, 3, 2))
  
##### Unnormalised data
boxplot(y[["comb"]]$noNorm, las=2, col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
title(main="Unnormalised data", ylab=params$transform)
legend("topright", legend=legend, fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", cex = 0.7, box.col="transparent")
  
##### Normalised data
boxplot(y[["comb"]]$norm, las=2, col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
title(main=paste0("Normalised data (", params$norm, ")"), ylab=params$transform)
legend("topright", legend=legend, fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", cex = 0.7, box.col="transparent")
invisible(dev.off())

##### Clean the space
rm(den, y)
##### The strategy for correcting data for batch effects is to consider the investigated sample and internal reference cohort as one group (batch) (regardless of the investigated patient tissue origin), and TCGA data (of any cancer type) as another batch. The objective is to remove as much as possible data variation due to technical factors.
batches <- as.character(ref_dataset.list[[dataset]][["sample_annot"]]$Dataset)

##### Change the sample dataset name to internal reference cohort
batches[ match(sample_name, batches) ] <- int_cancer_group

##### Perform batch-effect correctrion using limma
ref_dataset.list[[dataset]][["batch_effect_corrected"]] <- limma::removeBatchEffect(ref_dataset.list[[dataset]][["combined_data_processed"]], batch = batches)
suppressMessages(library(plotly))

##### Perform principal component analysis (PCA) using combined-only data and batch-effect corrected data
##### Loop through combined datasets and perform PCA
for ( dataset in names(ref_dataset.list) ) {
  target <- ref_dataset.list[[dataset]][["sample_annot"]]
  target$Dataset <- gsub(sample_name, "Patient", target$Dataset)
  target$Target <- gsub(sample_name, "Patient", target$Target)
  
  if ( params$batch_rm ) {
    ref_dataset.list[[dataset]][["pca_combined_data_processed"]] <- pca(data = ref_dataset.list[[dataset]][["combined_data_processed"]], targets = target, title = "Before batch-effects correction", report_dir = results_dir, suffix = "_before_batch_rm")
    
    ref_dataset.list[[dataset]][["pca_batch_effect_corrected"]] <- pca(data = ref_dataset.list[[dataset]][["batch_effect_corrected"]], targets = target, title = "After batch-effects correction", report_dir = results_dir, suffix = "_after_batch_rm")
    
    ref_dataset.list[[dataset]][["data_to_report"]] <- ref_dataset.list[[dataset]][["batch_effect_corrected"]]
    
  } else {
    ref_dataset.list[[dataset]][["pca_combined_data_processed"]] <- pca(data = ref_dataset.list[[dataset]][["combined_data_processed"]], targets = target, report_dir = results_dir)
    
    ref_dataset.list[[dataset]][["data_to_report"]] <- ref_dataset.list[[dataset]][["combined_data_processed"]]
  }
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
##### Generate relative log expression (RLE) plot using combined-only data and batch-effect corrected data
##### Loop through combined datasets and generate RLE plot
for ( dataset in names(ref_dataset.list) ) {
  target <- ref_dataset.list[[dataset]][["sample_annot"]]
  target$Dataset <- gsub(sample_name, "Patient", target$Dataset)
  target$Target <- gsub(sample_name, "Patient", target$Target)
  
  if ( params$batch_rm ) {
    par(mfrow=c(2,1), mar=c(2, 5, 3, 2))
    
    ##### Before batch-effects correction
    plotRLE(ref_dataset.list[[dataset]][["combined_data_processed"]], col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
    title(main="Before batch-effects correction", ylab="RLE")
    legend("topright", legend=levels(factor(target$Target)), fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
    
    ref_dataset.list[[dataset]][["rle_combined_data_processed"]] <- recordPlot()
    
    ##### After batch-effects correction
    plotRLE(ref_dataset.list[[dataset]][["batch_effect_corrected"]], col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
    title(main="After batch-effects correction", ylab="RLE")
    legend("topright", legend=levels(factor(target$Target)), fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
    
    ref_dataset.list[[dataset]][["rle_batch_effect_corrected"]] <- recordPlot()
    
    
    ##### Save the plot as png file
    png(paste0(PlotsDir, "/rle.png"), width=900, height=700, pointsize = 14)
    par(mfrow=c(2,1), mar=c(2, 5, 3, 2))
  
    ##### Before batch-effects correction
    plotRLE(ref_dataset.list[[dataset]][["combined_data_processed"]], col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
    title(main="Before batch-effects correction", ylab="RLE")
    legend("topright", legend=levels(factor(target$Target)), fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
  
    ##### After batch-effects correction
    plotRLE(ref_dataset.list[[dataset]][["batch_effect_corrected"]], col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
    title(main="After batch-effects correction", ylab="RLE")
    legend("topright", legend=levels(factor(target$Target)), fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
    invisible(dev.off())

  } else {
    plotRLE(ref_dataset.list[[dataset]][["combined_data_processed"]], col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
    title(main="", ylab="RLE")
    legend("topright", legend=levels(factor(target$Target)), fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
    
    ref_dataset.list[[dataset]][["rle_combined_data_processed"]] <- recordPlot()
    
    ##### Save the plot as png file
    png(paste0(PlotsDir, "/rle.png"), width=900, height=450, pointsize = 14)
  
    plotRLE(ref_dataset.list[[dataset]][["combined_data_processed"]], col=targets.colour[[2]], main="", pch="", las=3, xaxt="n", outline = FALSE)
    title(main="", ylab="RLE")
    legend("topright", legend=levels(factor(target$Target)), fill=targets.colour[[1]], horiz=TRUE, bg = "transparent", box.col="transparent")
    invisible(dev.off())
  }
}
#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())

##### Clean the space
rm(targets.colour, den, y)
##### Loop through combined, BUT NOT PROCESSED, datasets and annotate ALL genes. This part is mainly required for biotype detection step
for ( dataset in names(ref_dataset.list) ) {
  
  ##### Convert data into a data frame to make the Ensembl ID and gene symbol matches (with merge function)
  data <- ref_dataset.list[[dataset]][["combined_data"]]
  data.df <- as.data.frame(cbind(rownames(data), data))
  colnames(data.df)[1] <- "ENSEMBL"

  ##### Get genes annotation and genomic locations
  edb <- eval(parse(text = paste0("EnsDb.Hsapiens.v", params$ensembl_version)))
  
  ##### Get keytypes for gene SYMBOL
  keys <- keys(edb, keytype="GENEID")
  
  ##### Get genes genomic coordiantes
  gene_info <- ensembldb::select(edb, keys=keys, columns=c("GENEID", "GENEBIOTYPE", "GENENAME", "SEQNAME", "GENESEQSTART", "GENESEQEND"), keytype="GENEID")
  names(gene_info) <- gsub("GENEID", "ENSEMBL", names(gene_info))
  names(gene_info) <- gsub("GENENAME", "SYMBOL", names(gene_info))
  
  ##### Limit genes annotation to those genes for which sample expression measurments are available
  gene_info <-  gene_info[ gene_info$ENSEMBL %in% data.df$ENSEMBL,  ]
  
  ##### Remove rows with duplicated ENSEMBL IDs
  gene_info = gene_info[!duplicated(gene_info$ENSEMBL),]
  rownames(gene_info) <- gene_info$ENSEMBL
  
  ##### Remove rows with duplicated gene symbols (Y_RNAs, SNORs, LINC0s etc)
  gene_info = gene_info[!duplicated(gene_info$SYMBOL),]
  
  ##### Add info about immune response markers
  gene_info.immune_markers <- merge(gene_info, ref_genes.list[["genes_immune"]]$immune_markers, by = "SYMBOL", all.x = TRUE)
  
  ##### Keep only immune response markers for which there is available annotation
  ref_genes.list[["genes_immune"]]$immune_markers <- ref_genes.list[["genes_immune"]]$immune_markers[ ref_genes.list[["genes_immune"]]$immune_markers$SYMBOL %in% gene_info.immune_markers$SYMBOL, ]
  
  ##### Add info about immunogram genes
  if ( params$immunogram ) {
    gene_info.immunogram <- merge(gene_info, ref_genes.list[["genes_immune"]]$immunogram, by = "SYMBOL", all.x = TRUE)
    gene_info.immunogram <- gene_info.immunogram[!duplicated(gene_info.immunogram[,"ENSEMBL"]),]
    
    ##### Keep only immunogram genes for which there is available annotation
    ref_genes.list[["genes_immune"]]$immunogram <- ref_genes.list[["genes_immune"]]$immunogram[ ref_genes.list[["genes_immune"]]$immunogram$SYMBOL %in% gene_info.immunogram$SYMBOL, ]
    
    ##### Merge genes annotations for immunogram genes and immune markers
    gene_info <- merge( gene_info.immunogram, gene_info.immune_markers[ , c("ENSEMBL", "Immune_Cycle_Role") ], by = "ENSEMBL")
  } else {
    gene_info <- gene_info.immune_markers
  }
  
  ##### Merge genes genomic coordinates info with their annotation and expression data
  data.annot <- merge(gene_info, data.df, by = "ENSEMBL", all.x = FALSE)
  rownames(data.annot) <- data.annot$ENSEMBL
  
  ##### Get data matrix with gene symbols
  if ( params$immunogram ) {
    ref_dataset.list[[dataset]][["gene_annot_all"]] <- data.annot[, c("SYMBOL", "GENEBIOTYPE", "ENSEMBL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "CIC", "Immune_Cycle_Role")]
  } else {
    ref_dataset.list[[dataset]][["gene_annot_all"]] <- data.annot[, c("SYMBOL", "GENEBIOTYPE", "ENSEMBL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "Immune_Cycle_Role")]
  }
  
  ##### Save the combined expression matrix, genes list and associated targets into txt files
  write.table(prepare2write(ref_dataset.list[[dataset]][["combined_data"]]), file = paste0(results_dir, "/", sample_name, ".RNAseq_report.combined_data.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=TRUE, append = FALSE )
  write.table(prepare2write(ref_dataset.list[[dataset]][["gene_annot_all"]]), file = paste0(results_dir, "/", sample_name, ".RNAseq_report.gene_annot_all.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=TRUE, append = FALSE )
}

##### Clean the space
rm(data, target, data.df, edb, keys)
##### Loop through combined datasets and annotate genes
for ( dataset in names(ref_dataset.list) ) {
  
  ##### Convert data into a data frame to make the Ensembl ID and gene symbol matches (with merge function)
  data <- ref_dataset.list[[dataset]][["data_to_report"]]
  data.df <- as.data.frame(cbind(rownames(data), data))
  colnames(data.df)[1] <- "ENSEMBL"
  
  ##### Merge genes genomic coordinates info with their annotation and expression data
  data.annot <- merge(gene_info, data.df, by = "ENSEMBL", all.x = FALSE)
  
  ##### Keep only genes fo which gene symbol is available
  data.annot <- data.annot[!(is.na(data.annot$SYMBOL) | data.annot$SYMBOL==""), ]
  rownames(data.annot) <- data.annot$SYMBOL
  
  ##### Get data matrix with gene symbols
  ref_dataset.list[[dataset]][["data_to_report"]] <- apply(data.annot[, colnames(data)], 2, as.numeric)
  rownames(ref_dataset.list[[dataset]][["data_to_report"]]) <- data.annot$SYMBOL
  ref_dataset.list[[dataset]][["gene_annot"]] <- data.annot[, c("SYMBOL", "GENEBIOTYPE", "ENSEMBL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "Immune_Cycle_Role")]
  
  ##### Save the combined expression matrix, genes list and associated targets into txt files
  write.table(prepare2write(ref_dataset.list[[dataset]][["data_to_report"]]), file = paste0(results_dir, "/", sample_name, ".RNAseq_report.combined_data_processed.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=TRUE, append = FALSE )
  write.table(prepare2write(toupper(rownames(ref_dataset.list[[dataset]][["data_to_report"]]))), file = paste0(results_dir, "/", sample_name, ".RNAseq_report.combined_data_processed.genes.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=TRUE, append = FALSE )
  write.table(prepare2write(ref_dataset.list[[dataset]][["sample_annot"]]), file = paste0(results_dir, "/", sample_name, ".RNAseq_report.sample_annot.txt"), sep="\t", quote=FALSE, row.names=FALSE, col.names=TRUE, append = FALSE )
}

##### Clean the space
rm(data, data.df, gene_info)
##### Save the entire expression data for all genes measured in patient's sample with cancer genes annotaiton as a data table html file
##### Generate expression summary table for mutated genes
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]

##### Percentiles
genes.expr.perc <- exprTable( genes = rownames(data), keep_all = TRUE, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]], ext_links = TRUE, type = "perc", scaling = scaling)

##### Z-scores
genes.expr.z <- exprTable( genes = rownames(data), keep_all = TRUE, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]], ext_links = TRUE, type = "z", scaling = scaling)

##### Create directory for saving tables
exprTableDir <- paste(results_dir, "exprTables", sep = "/")
    
if ( !file.exists(exprTableDir) ) {
  dir.create(exprTableDir, recursive=TRUE)
}

##### Save the expression tables as html file
saveWidgetFix(widget=genes.expr.perc[[1]], file=paste(exprTableDir, "genes.expr.perc.html", sep = "/"), selfcontained=TRUE)
saveWidgetFix(widget=genes.expr.z[[1]], file=paste(exprTableDir, "genes.expr.z.html", sep = "/"), selfcontained=TRUE)

##### Clean the space
rm(data, targets, genes.expr.z, genes.expr.perc)
##### Combine expression data with mutation and CN data if available
cn_data <- ref_genes.list[["purple"]]
expr_data <- ref_dataset.list[[dataset]][["data_to_report"]]
targets <- ref_dataset.list[[dataset]][["sample_annot"]]

##### ...percerntiles
expr_data.perc <- exprTable( genes = rownames(expr_data), keep_all = TRUE, data = expr_data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, type = "perc", scaling = scaling)[[2]]

expr_genes <- expr_data.perc$SYMBOL

##### Get the "Diff" (Patient vs [comp_cancer]) Z-scores using exprTable function
expr_data.z <- exprTable( genes = expr_genes, keep_all = TRUE, data = expr_data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, type = "z", scaling = scaling)[[2]]

##### Make sure the tables have the same genes order
expr_data.perc <- expr_data.perc[ expr_genes, ]

if ( comp_cancer_group != int_cancer_group ) {
  expr_data.perc <- expr_data.perc[, "Diff" ]
  expr_data.z <- expr_data.z[, "Diff" ]
} else {
  expr_data.perc <- expr_data.perc[, paste0( "Patient vs ", comp_cancer_group)]
  expr_data.z <- expr_data.z[, paste0( "Patient vs ", comp_cancer_group)]
}

names(expr_data.perc) <- expr_genes
names(expr_data.z) <- expr_genes

##### Calculate the mean CN for each gene
cn_data$MeanCopyNumber <- rowMeans(cbind(cn_data$MinCopyNumber, cn_data$MaxCopyNumber))
  
##### Deal with negative CN values
cn_data$MeanCopyNumber[ cn_data$MeanCopyNumber < 0 ] <- 0

##### Remove entries with missing gene symbol (mainly variants in intergenic regions)
cn_data <- cn_data[ cn_data$Gene %!in% "", ]

##### Keep only altered genes with CN values below loss threshold (default 5th percentile) and above gain threshold (default 95th percentile)
cn_data.all <- cn_data

##### Get the percentiles from from the CN values
cn_data.all.percent <- quantile(cn_data.all$MeanCopyNumber, probs = seq(0, 1, .05), na.rm = TRUE)

##### Keep only genes with available expression data
cn_data <- cn_data[ cn_data$Gene %in% names(expr_data.z), ]

##### Add mutation data if available
if ( !is.null(ref_genes.list[["pcgr"]]) ) {
  mut_data <- ref_genes.list[["pcgr"]]
  
  ##### Remove entries with missing gene symbol (mainly variants in intergenic regions)
  mut_data <- mut_data[ mut_data$SYMBOL %!in% "", ]

  ##### Prepare mutation data to include multiple mutations per gene
  ##### Initiate variable for the gene mutation status for each gene
  gene.mut <- as.matrix(rep("None", length(expr_data.z)))
  colnames(gene.mut) <- "Alterations"
  rownames(gene.mut) <- names(expr_data.z)

  for ( i in 1:nrow(gene.mut) ) {
    ##### Check if any mutations are reported for each gene
    if (  rownames(gene.mut)[i] %in% mut_data$SYMBOL ) {
    
      ##### Deal with multiple mutations per gene
      if ( length(mut_data[ mut_data$SYMBOL %in% rownames(gene.mut)[i],  ]$CONSEQUENCE) > 1 ) {
        gene.mut[ rownames(gene.mut)[i],"Alterations" ] <- "Mutation: multiple hits"
      } else {
        gene.mut[ rownames(gene.mut)[i],"Alterations" ] <- paste0("Mutation: ", mut_data[ mut_data$SYMBOL %in% rownames(gene.mut)[i],  ]$CONSEQUENCE)
      }
    }
  }

  ##### If there is no expression value for a specific gene than assume it's not expressed at all and assign the lowest value observed in that sample
  for ( gene in unique(mut_data$SYMBOL) ) {
    if ( gene %!in% rownames(gene.mut) ) {
      
      expr_data.perc <- c(expr_data.perc, min(expr_data.perc))
      names(expr_data.perc)[length(expr_data.perc)] <- gene
      
      expr_data.z <- c(expr_data.z, min(expr_data.z))
      names(expr_data.z)[length(expr_data.z)] <- gene
      
      ##### Deal with multiple mutations per gene
      if ( length(mut_data[ mut_data$SYMBOL %in% gene,  ]$CONSEQUENCE) > 1 ) {
        gene.mut <- rbind( gene.mut,  "multiple hits")
      } else {
        gene.mut <- rbind( gene.mut,  mut_data[ mut_data$SYMBOL %in% gene,  ]$CONSEQUENCE )
      }
      rownames(gene.mut)[nrow(gene.mut)] <- gene
    }
  }

  ##### Subset expression, mutation and copy-number data to include only overlapping genes
  genes.intersect <- intersect(intersect(rownames(gene.mut), cn_data$Gene), names(expr_data.perc))
  
  gene.mut.sub <- gene.mut[ rownames(gene.mut) %in% genes.intersect, ]
  cn_data.sub <- cn_data[ cn_data$Gene %in% genes.intersect, ]
  expr_data.perc.sub <- expr_data.perc[ names(expr_data.perc) %in% genes.intersect ]
  expr_data.z.sub <- expr_data.z[ names(expr_data.z) %in% genes.intersect ]
  
  ##### Make sure thay are all in the same order
  gene.mut.sub <- gene.mut.sub[ genes.intersect ]
  rownames(cn_data.sub) <- cn_data.sub$Gene
  cn_data.sub <- cn_data.sub[ genes.intersect,  ]
  expr_data.perc.sub <- expr_data.perc.sub[ genes.intersect  ]
  expr_data.z.sub <- expr_data.z.sub[ genes.intersect  ]
  
  ##### Prepare data frame
  cn_data.sub <- data.frame(names(expr_data.z.sub), cn_data.sub$MeanCopyNumber, expr_data.perc.sub, expr_data.z.sub, gene.mut.sub)
  colnames(cn_data.sub) <- c("Gene", "CN", "Perc_diff", "Z_score_diff", "Alterations")
  
} else {
  ##### Skip the step for processing mutation info and deal with expression and copy-number data
  ##### Subset expression and copy-number data to include only overlapping genes
  genes.intersect <- intersect(cn_data$Gene, names(expr_data.perc))
  
  cn_data.sub <- cn_data[ cn_data$Gene %in% genes.intersect, ]
  expr_data.perc.sub <- expr_data.perc[ names(expr_data.perc) %in% genes.intersect ]
  expr_data.z.sub <- expr_data.z[ names(expr_data.z) %in% genes.intersect ]
  
  ##### Make sure thay are all in the same order
  rownames(cn_data.sub) <- cn_data.sub$Gene
  cn_data.sub <- cn_data.sub[ genes.intersect,  ]
  expr_data.perc.sub <- expr_data.perc.sub[ genes.intersect  ]
  expr_data.z.sub <- expr_data.z.sub[ genes.intersect  ]
  
  ##### Prepare data frame
  cn_data.sub <- data.frame(names(expr_data.z.sub), cn_data.sub$MeanCopyNumber, expr_data.perc.sub, expr_data.z.sub)
  colnames(cn_data.sub) <- c("Gene", "CN", "Perc_diff", "Z_score_diff")
}

ref_dataset.list[[dataset]][["expr_mut_cn_data_all"]] <- cn_data.sub

##### Limit the data to include only cancer genes
cn_data.sub <- cn_data.sub[ cn_data.sub$Gene %in% rownames(ref_genes.list[["genes_cancer"]]), ]

##### Keep genes meeting the user-defined CN values thresholds
ref_dataset.list[[dataset]][["expr_mut_cn_data"]] <- cn_data.sub[ cn_data.sub$CN <= cn_bottom | cn_data.sub$CN >= cn_top, ]

##### Clean the space
rm(cn_data, cn_data.sub, expr_data, gene.mut, mut_data, targets, expr_data.z, expr_data.perc, expr_data.z.sub, expr_data.perc.sub, expr_genes, gene.mut.sub, genes.intersect)
suppressMessages(library(plotly))

##### Draw histogram of CN data
cn_dist_plot <- plot_ly(x = cn_data.all$MeanCopyNumber, type = 'histogram', name = "CN data", width = 800, height = 300) %>%
  
  ##### Add 5th percentile threshold
  add_lines(y = seq(0,1000, 100), x = rep(cn_data.all.percent[2],11), 
              line = list(color = "black", dash = "dash"), opacity = 0.4,
              name = "5th percentile", showlegend = TRUE) %>%
  
  ##### Add 50th percentile
  add_lines(y = seq(0,1000, 100), x = rep(cn_data.all.percent[11],11), 
              line = list(color = "black", dash = "dash"), opacity = 0.7,
              name = "50th percentile", showlegend = TRUE) %>%
  
  ##### Add 95th percentile threshold
  add_lines(y = seq(0,1000, 100), x = rep(cn_data.all.percent[20],11), 
              line = list(color = "black", dash = "dash"), opacity = 1,
              name = "95th percentile", showlegend = TRUE) %>%
  
  layout(xaxis = list( title = "CN values"), yaxis = list( title = "Frequency"), margin = list(l=50, r=50, b=50, t=50, pad=4), autosize = F)

##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())

##### Clean the space
rm(cn_data.all)
##### Flag known fusions based on info from Cancer Biomarkers database (CGI) (https://www.cancergenomeinterpreter.org/biomarkers)
known_translocations.CGI <- caner_genes_annot.list[["cancer_biomarkers_trans"]]
known_translocations.CGI$cancer_acronym <- gsub(";", ", ", known_translocations.CGI$cancer_acronym)
known_translocations.CGI$source <- gsub(";", ", ", known_translocations.CGI$source)
known_translocations.CGI$translocation <- gsub("__", "_", known_translocations.CGI$translocation)
  
##### Flag known fusions based on info from FusionGDB (https://ccsm.uth.edu/FusionGDB)
known_translocations.FusionGDB <- caner_genes_annot.list[["FusionGDB"]]
  
##### Merge info from both resources
known_translocations <- merge(known_translocations.FusionGDB, known_translocations.CGI, by.x = "FGname", by.y = "translocation", all = TRUE, sort=FALSE)
  
##### Extract gene pairs involved in reported gene fusions
trans.pairs <- as.data.frame(cbind( known_translocations$FGname, known_translocations$FGname ))
names(trans.pairs) <- c("geneA", "geneB")
trans.pairs$geneA <- sub("_.*", "", trans.pairs$geneA)
trans.pairs$geneB <- sub(".*_", "", trans.pairs$geneB)
known_translocations <- cbind(known_translocations, trans.pairs)
trans.pairs <- apply( trans.pairs , 1 , paste , collapse = "-" )
##### Read in the arriba fusion calls
arriba.fusions <- ref_genes.list[["arriba"]]
colnames(arriba.fusions) <- gsub("X.gene1", "geneA", colnames(arriba.fusions))
colnames(arriba.fusions) <- gsub("1", "A", colnames(arriba.fusions))
colnames(arriba.fusions) <- gsub("2", "B", colnames(arriba.fusions))

#####  Note the fusions order, which will be later required for imbedding Arriba plots from corresponding pdf booklet pages
arriba.fusions.order <- paste(arriba.fusions$geneA, arriba.fusions$geneB, sep="__")

##### Extract only those fusion genes that are in cancer genes list
arriba.cancer_genes <- data.frame()

for (row in 1:nrow(arriba.fusions)){
  if(arriba.fusions[row,"geneA"] %in% rownames(ref_genes.list[["genes_cancer"]]) | arriba.fusions[row,"geneB"] %in% rownames(ref_genes.list[["genes_cancer"]])) {
    
    ##### Creating a new dataframe for extracting arriba rows with cancer gene hits
    arriba.cancer_genes <- rbind(arriba.cancer_genes, data.frame(arriba.fusions[row,]))
  }
}

##### Add columns for info about reported fusions
fusions <- cbind(arriba.fusions, data.frame(matrix("", ncol = 5, nrow = nrow(arriba.fusions)), stringsAsFactors = FALSE))
colnames(fusions)[(ncol(fusions)-4):ncol(fusions)] <- c("FGID", "reported_fusion", "reported_fusion_geneA", "reported_fusion_geneB", "effector_gene")
  
##### Add annotations about known fusion events
##### Loop through all genes involved in deteced gene fusions (arriba results) and check which are already reported
for ( i in 1:nrow(fusions) ) {
  geneA <- as.character(fusions$geneA[i])
  geneB <- as.character(fusions$geneB[i])
          
  ##### First check if the exact reported gene pairs were detected by arriba
  if ( paste(geneA, geneB, sep="-") %in% trans.pairs ) {
      
    ##### provide fusion URL to FusionGDB
    fusions$reported_fusion[i] <- "Yes"
      
    ##### provide fusion ID from FusionGDB
    fusions$FGID[i] <- known_translocations$FGID[ trans.pairs %in% paste(geneA, geneB, sep="-")  ]
      
    fusions$reported_fusion_geneA[i] <- "Yes"
    fusions$reported_fusion_geneB[i] <- "Yes"
      
  } else if ( paste(geneB, geneA, sep="-") %in% trans.pairs ) {
      
    ##### provide fusion URL to FusionGDB
    fusions$reported_fusion[i] <- "Yes"
      
    ##### provide fusion ID from FusionGDB
    fusions$FGID[i] <- known_translocations$FGID[ trans.pairs %in% paste(geneB, geneA, sep="-")  ]
      
    fusions$reported_fusion_geneA[i] <- "Yes"
    fusions$reported_fusion_geneB[i] <- "Yes"
      
  ##### Now check if any ofthe arriba detected fusion genes are reported
  } else {
    fusions$reported_fusion[i] <- "-"
      
    ##### Check the Cancer Genome Interpreter (CGI) database first
    ##### Check arriba genes A and genes A in reported fusions
    if ( geneA %in% known_translocations$geneA ) {
       fusions$reported_fusion_geneA[i] <- "Yes"
        
    ##### Check arriba genes A and genes B in reported fusions
    } else if ( geneA %in% known_translocations$geneB ) {
      fusions$reported_fusion_geneA[i] <- "Yes"
    }
      
    ##### Check arriba genes B and genes A in reported fusions
    if ( geneB %in% known_translocations$geneA ) {
      fusions$reported_fusion_geneB[i] <- "Yes"
        
    ##### Check arriba genes B and genes B in reported fusions
    } else if ( geneB %in% known_translocations$geneB ) {
      fusions$reported_fusion_geneB[i] <- "Yes"
    }
      
    ##### Flag if any of the genes are effector gene
    if ( geneA %in% known_translocations$effector_gene  ) {
      fusions$effector_gene[i] <- geneA
    } else if ( geneB == known_translocations$effector_gene  ) {
      fusions$effector_gene[i] <- geneB
    }
  }
}

##### Sum split reads in gene A and B
fusions$split_reads <- fusions$split_readsA + fusions$split_readsB

##### Add column indicating fusions containing known cancer genes
fusions$fusions_cancer <- c(rep("-", nrow(fusions)))

if ( nrow(arriba.cancer_genes) > 0 ) {
  fusions$fusions_cancer[ fusions$geneA %in% arriba.cancer_genes$geneA ] <- "Yes"
  fusions$fusions_cancer[ fusions$geneB %in% arriba.cancer_genes$geneB ] <- "Yes"
}

##### Re-ordering arriba's results on the basis of Arriba's confidence, reported fusions and then read count values (first by split count and then paircount) and then involvment of cancer genes and reported one of the fusion genes
fusions <- fusions[ order(fusions$reported_fusion, fusions$split_reads, fusions$split_readsA, fusions$split_readsB, fusions$discordant_mates, fusions$fusions_cancer, fusions$reported_fusion_geneA, fusions$reported_fusion_geneB, decreasing = TRUE), ]
fusions <- fusions[order(factor(fusions$confidence, levels=c("high", "medium", "low"))), ]

##### Keep only key columns and add info about Arriba detected fusions and 
fusions <- fusions[ colnames(fusions) %in% c("geneA", "geneB", "breakpointA", "breakpointB", "siteA", "siteB", "type", "split_reads", "split_readsA", "split_readsB", "discordant_mates", "confidence", "FGID", "reported_fusion", "reported_fusion_geneA", "reported_fusion_geneB", "effector_gene", "fusions_cancer")]

##### Add column to flag fusions supported by WGS data (from MANTA), if available
fusions$geneA_dna_support <- "-"
fusions$geneB_dna_support <- "-"

if ( runPizzlyChunk || runDragenFusionChunk ) {
  fusions$Arriba <- c(rep("Yes", nrow(fusions)))
}

##### Clean the space and return output
rm(arriba.fusions, arriba.fusion.transcripts, arriba.cancer_genes, arriba.other_genes)
##### Read in the arriba fusion calls
dragen.fusions <- ref_genes.list[["dragenFusion"]]
colnames(dragen.fusions) <- gsub("gene1", "geneA", colnames(dragen.fusions))
colnames(dragen.fusions) <- gsub("1", "A", colnames(dragen.fusions))
colnames(dragen.fusions) <- gsub("2", "B", colnames(dragen.fusions))

##### Extract only those fusion genes that are in cancer genes list
dragen.cancer_genes <- data.frame()

for (row in 1:nrow(dragen.fusions)){
  if(dragen.fusions[row,"geneA"] %in% rownames(ref_genes.list[["genes_cancer"]]) | dragen.fusions[row,"geneB"] %in% rownames(ref_genes.list[["genes_cancer"]])) {
    
    ##### Creating a new dataframe for extracting dragen rows with cancer gene hits
    dragen.cancer_genes <- rbind(dragen.cancer_genes, data.frame(dragen.fusions[row,]))
  }
}

##### Add columns for info about reported fusions
dragen.fusions <- cbind(dragen.fusions, data.frame(matrix("", ncol = 5, nrow = nrow(dragen.fusions)), stringsAsFactors = FALSE))
colnames(dragen.fusions)[(ncol(dragen.fusions)-4):ncol(dragen.fusions)] <- c("FGID", "reported_fusion", "reported_fusion_geneA", "reported_fusion_geneB", "effector_gene")

##### Add annotations about known fusion events
##### Loop through all genes involved in deteced gene fusions (dragen results) and check which are already reported
for ( i in 1:nrow(dragen.fusions) ) {
  geneA <- as.character(dragen.fusions$geneA[i])
  geneB <- as.character(dragen.fusions$geneB[i])
          
  ##### First check if the exact reported gene pairs were detected by dragen
  if ( paste(geneA, geneB, sep="-") %in% trans.pairs ) {
      
    ##### provide fusion URL to FusionGDB
    dragen.fusions$reported_fusion[i] <- "Yes"
      
    ##### provide fusion ID from FusionGDB
    dragen.fusions$FGID[i] <- known_translocations$FGID[ trans.pairs %in% paste(geneA, geneB, sep="-")  ]
      
    dragen.fusions$reported_fusion_geneA[i] <- "Yes"
    dragen.fusions$reported_fusion_geneB[i] <- "Yes"
      
  } else if ( paste(geneB, geneA, sep="-") %in% trans.pairs ) {
      
    ##### provide fusion URL to FusionGDB
    dragen.fusions$reported_fusion[i] <- "Yes"
      
    ##### provide fusion ID from FusionGDB
    dragen.fusions$FGID[i] <- known_translocations$FGID[ trans.pairs %in% paste(geneB, geneA, sep="-")  ]
      
    dragen.fusions$reported_fusion_geneA[i] <- "Yes"
    dragen.fusions$reported_fusion_geneB[i] <- "Yes"
      
  ##### Now check if any of the dragen detected fusion genes are reported
  } else {
    dragen.fusions$reported_fusion[i] <- "-"
      
    ##### Check the Cancer Genome Interpreter (CGI) database first
    ##### Check dragen genes A and genes B in reported fusions
    if ( geneA %in% known_translocations$geneA ) {
       dragen.fusions$reported_fusion_geneA[i] <- "Yes"
        
    ##### Check dragen genes A and genes B in reported fusions
    } else if ( geneA %in% known_translocations$geneB ) {
      dragen.fusions$reported_fusion_geneA[i] <- "Yes"
    }
      
    ##### Check dragen genes B and genes A in reported fusions
    if ( geneB %in% known_translocations$geneA ) {
      dragen.fusions$reported_fusion_geneB[i] <- "Yes"
        
    ##### Check dragen genes B and genes A in reported fusions
    } else if ( geneB %in% known_translocations$geneB ) {
      dragen.fusions$reported_fusion_geneB[i] <- "Yes"
    }
      
    ##### Flag if any of the genes are effector gene
    if ( geneA %in% known_translocations$effector_gene  ) {
      dragen.fusions$effector_gene[i] <- geneA
    } else if ( geneB == known_translocations$effector_gene  ) {
      dragen.fusions$effector_gene[i] <- geneB
    }
  }
}

##### Add column indicating fusions containing known cancer genes
dragen.fusions$fusions_cancer <- c(rep("-", nrow(dragen.fusions)))

if ( nrow(dragen.cancer_genes) > 0 ) {
  dragen.fusions$fusions_cancer[ dragen.fusions$geneA %in% dragen.cancer_genes$geneA ] <- "Yes"
  dragen.fusions$fusions_cancer[ dragen.fusions$geneB %in% dragen.cancer_genes$geneB ] <- "Yes"
}

##### Re-ordering dragen's results on the basis of Dragen's confidence, reported fusions and then score (as Dragen doesn't includes split count and paircount info) and then involvment of cancer genes and reported one of the fusion genes
dragen.fusions <- dragen.fusions[ order(dragen.fusions$reported_fusion, dragen.fusions$Score, dragen.fusions$fusions_cancer, dragen.fusions$reported_fusion_geneA, dragen.fusions$reported_fusion_geneB, decreasing = TRUE), ]
#dragen.fusions <- dragen.fusions[order(factor(dragen.fusions$confidence, levels=c("high", "medium", "low"))), ]

##### Keep only key columns
dragen.fusions <- dragen.fusions[ colnames(dragen.fusions) %in% c("geneA", "geneB", "Score", "LeftBreakpoint", "RightBreakpoint", "GeneALocation", "GeneBLocation", "NumSplitReads", "NumSoftClippedReads", "FGID", "reported_fusion", "reported_fusion_geneA", "reported_fusion_geneB", "effector_gene", "fusions_cancer")]

##### Add column to flag fusions supported by WGS data (from MANTA), if available
dragen.fusions$geneA_dna_support <- "-"
dragen.fusions$geneB_dna_support <- "-"

##### Add results from Arriba
if ( runArribaChunk ) {
  
  #####  Dragen's fusion format version 3.9.3
  if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(dragen.fusions)) ) {

    ##### Add column with Dragen fusions
    dragen.fusions$fusion <- paste(dragen.fusions$geneA, dragen.fusions$geneB, sep="__")
    fusions$Dragen <- c(rep("-", nrow(fusions)))
    fusions$split_reads <- fusions$split_readsA + fusions$split_readsB
    fusions$soft_clipped_reads <- c(rep("-", nrow(fusions)))
    fusions$score <- c(rep("-", nrow(fusions)))
    
    ##### Re-order columns
    fusions <- fusions %>% dplyr::relocate(split_reads, .before = split_readsA)
    fusions <- fusions %>% dplyr::relocate(soft_clipped_reads, .before = confidence)
    fusions <- fusions %>% dplyr::relocate(score, .before = FGID)
    
    ##### Loop through Dragen results, mark fusions detected by both tools. For those detected only by Dragen adapt results format to Arriba results
    for ( i in 1:nrow(dragen.fusions) ) {
      
      if ( !is.na(match(dragen.fusions$fusion[i], paste(fusions$geneA, fusions$geneB, sep="__"))) ) {
        fusions$Dragen[ match(dragen.fusions$fusion[i], paste(fusions$geneA, fusions$geneB, sep="__")) ] <- "Yes"
        dragen.fusions[ i, ] <-  rep("-", ncol(dragen.fusions))
      } else {
        fusions <- rbind(fusions, data.frame(geneA=dragen.fusions$geneA[i],geneB=dragen.fusions$geneB[i], breakpointA=dragen.fusions$LeftBreakpoint[i], breakpointB=dragen.fusions$RightBreakpoint[i], siteA=dragen.fusions$GeneALocation[i], siteB=dragen.fusions$GeneBLocation[i], type="-", split_reads=dragen.fusions$NumSplitReads[i], split_readsA="-", split_readsB="-", discordant_mates="-", soft_clipped_reads=dragen.fusions$NumSoftClippedReads[i], confidence="-", score=dragen.fusions$Score[i], FGID=dragen.fusions$FGID[i], reported_fusion=dragen.fusions$reported_fusion[i], reported_fusion_geneA=dragen.fusions$reported_fusion_geneA[i], reported_fusion_geneB=dragen.fusions$reported_fusion_geneB[i], effector_gene=dragen.fusions$effector_gene[i], fusions_cancer=dragen.fusions$fusions_cancer[i], geneA_dna_support="-", geneB_dna_support="-", Arriba="-", Dragen="Yes" ))
      }
    }
  
  #####  Dragen's fusion format prior to version 3.9.3
  } else {
    
    ##### Add column with Dragen fusions
    dragen.fusions$fusion <- paste(dragen.fusions$geneA, dragen.fusions$geneB, sep="__")
    fusions$Dragen <- c(rep("-", nrow(fusions)))
    fusions$split_reads <- fusions$split_readsA + fusions$split_readsB
    fusions$score <- c(rep("-", nrow(fusions)))
    
    ##### Re-order columns
    fusions <- fusions %>% dplyr::relocate(split_reads, .before = split_readsA)
    fusions <- fusions %>% dplyr::relocate(score, .before = FGID)
      
    ##### Loop through Dragen results, mark fusions detected by both tools. For those detected only by Dragen adapt results format to Arriba results
    for ( i in 1:nrow(dragen.fusions) ) {
        
      if ( !is.na(match(dragen.fusions$fusion[i], paste(fusions$geneA, fusions$geneB, sep="__"))) ) {
        fusions$Dragen[ match(dragen.fusions$fusion[i], paste(fusions$geneA, fusions$geneB, sep="__")) ] <- "Yes"
        dragen.fusions[ i, ] <-  rep("-", ncol(dragen.fusions))
      } else {
        fusions <- rbind(fusions, data.frame(geneA=dragen.fusions$geneA[i],geneB=dragen.fusions$geneB[i], breakpointA=dragen.fusions$LeftBreakpoint[i], breakpointB=dragen.fusions$RightBreakpoint[i], siteA="-", siteB="-", type="-", split_reads="-", split_readsA="-", split_readsB="-", discordant_mates="-", confidence="-", score=dragen.fusions$Score[i], FGID=dragen.fusions$FGID[i], reported_fusion=dragen.fusions$reported_fusion[i], reported_fusion_geneA=dragen.fusions$reported_fusion_geneA[i], reported_fusion_geneB=dragen.fusions$reported_fusion_geneB[i], effector_gene=dragen.fusions$effector_gene[i], fusions_cancer=dragen.fusions$fusions_cancer[i], geneA_dna_support="-", geneB_dna_support="-", Arriba="-", Dragen="Yes" ))
      }
    }
  }
  
##### Otherwise add empty columns expected from Aribba results
} else {
  fusions <- dragen.fusions
  
  #####  Dragen's fusion format version 3.9.3
  if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(dragen.fusions)) ) {
    
    ##### Rename columns
    names(fusions) <- gsub("LeftBreakpoint", "breakpointA", names(fusions))
    names(fusions) <- gsub("RightBreakpoint", "breakpointB", names(fusions))
    names(fusions) <- gsub("GeneALocation", "siteA", names(fusions))
    names(fusions) <- gsub("GeneBLocation", "siteB", names(fusions))
    names(fusions) <- gsub("NumSplitReads", "split_reads", names(fusions))
    names(fusions) <- gsub("NumSoftClippedReads", "soft_clipped_reads", names(fusions))
    names(fusions) <- gsub("Score", "score", names(fusions))
  
  #####  Dragen's fusion format prior to version 3.9.3
  } else {
    
    ##### Rename columns
    names(fusions) <- gsub("LeftBreakpoint", "breakpointA", names(fusions))
    names(fusions) <- gsub("RightBreakpoint", "breakpointB", names(fusions))
    names(fusions) <- gsub("Score", "score", names(fusions))
  }
}

##### Clean the space and return output
rm(dragen.fusion.transcripts, dragen.cancer_genes, dragen.other_genes)
##### Read in the pizzly fusion calls
pizzly.fusion.candidates <- ref_genes.list[["pizzly"]]

##### Extract only those fusion genes that are in cancer genes list
pizzly.cancer_genes <- data.frame()

for (row in 1:nrow(pizzly.fusion.candidates)){
  if(pizzly.fusion.candidates[row,"geneA.name"] %in% rownames(ref_genes.list[["genes_cancer"]]) | pizzly.fusion.candidates[row,"geneB.name"] %in% rownames(ref_genes.list[["genes_cancer"]])) {
    
    ##### Creating a new dataframe for extracting pizzly rows with cancer gene hits
    pizzly.cancer_genes <- rbind(pizzly.cancer_genes, data.frame(pizzly.fusion.candidates[row,]))
  }
}

##### Extracting rows from pizzly results that are not cancer genes list
pizzly.other_genes <- pizzly.fusion.candidates[ rownames(pizzly.fusion.candidates) %!in% rownames(pizzly.cancer_genes), ]
  
##### Combing all the three above sorted dataframes
pizzly.fusions <- rbind(pizzly.cancer_genes, pizzly.other_genes)
  
##### Flag known fusions based on info from Cancer Biomarkers database (CGI) and FusionGDB (https://ccsm.uth.edu/FusionGDB)
##### Add columns for info about reported fusions
pizzly.fusions <- cbind(pizzly.fusions, data.frame(matrix("", ncol = 5, nrow = nrow(pizzly.fusions)), stringsAsFactors = FALSE))
colnames(pizzly.fusions)[(ncol(pizzly.fusions)-4):ncol(pizzly.fusions)] <- c("FGID", "reported_fusion", "reported_fusion_geneA", "reported_fusion_geneB", "effector_gene")
  
##### Add annotations about known fusion events
##### Loop through all genes involved in deteced gene fusions (pizzly results) and check which are already reported
for ( i in 1:nrow(pizzly.fusions) ) {
  geneA <- as.character(pizzly.fusions$geneA.name[i])
  geneB <- as.character(pizzly.fusions$geneB.name[i])
          
  ##### First check if the exact reported gene pairs were detected by pizzly
  if ( paste(geneA, geneB, sep="-") %in% trans.pairs ) {
      
    ##### provide fusion URL to FusionGDB
    pizzly.fusions$reported_fusion[i] <- "Yes"
      
    ##### provide fusion ID from FusionGDB
    pizzly.fusions$FGID[i] <- known_translocations$FGID[ trans.pairs %in% paste(geneA, geneB, sep="-")  ]
      
    pizzly.fusions$reported_fusion_geneA[i] <- "Yes"
    pizzly.fusions$reported_fusion_geneB[i] <- "Yes"
      
  } else if ( paste(geneB, geneA, sep="-") %in% trans.pairs ) {
      
    ##### provide fusion URL to FusionGDB
    pizzly.fusions$reported_fusion[i] <- "Yes"
      
    ##### provide fusion ID from FusionGDB
    pizzly.fusions$FGID[i] <- known_translocations$FGID[ trans.pairs %in% paste(geneB, geneA, sep="-")  ]
      
    pizzly.fusions$reported_fusion_geneA[i] <- "Yes"
    pizzly.fusions$reported_fusion_geneB[i] <- "Yes"
      
  ##### Now check if any ofthe pizzly detected fusion genes are reported
  } else {
    pizzly.fusions$reported_fusion[i] <- "-"
      
    ##### Check the Cancer Genome Interpreter (CGI) database first
    ##### Check pizzly genes A and genes A in reported fusions
    if ( geneA %in% known_translocations$geneA ) {
       pizzly.fusions$reported_fusion_geneA[i] <- "Yes"
        
    ##### Check pizzly genes A and genes B in reported fusions
    } else if ( geneA %in% known_translocations$geneB ) {
      pizzly.fusions$reported_fusion_geneA[i] <- "Yes"
    }
      
    ##### Check pizzly genes B and genes A in reported fusions
    if ( geneB %in% known_translocations$geneA ) {
      pizzly.fusions$reported_fusion_geneB[i] <- "Yes"
        
    ##### Check pizzly genes B and genes B in reported fusions
    } else if ( geneB %in% known_translocations$geneB ) {
      pizzly.fusions$reported_fusion_geneB[i] <- "Yes"
    }
      
    ##### Flag if any of the genes are effector gene
    if ( geneA %in% known_translocations$effector_gene  ) {
      pizzly.fusions$effector_gene[i] <- geneA
    } else if ( geneB == known_translocations$effector_gene  ) {
      pizzly.fusions$effector_gene[i] <- geneB
    }
  }
}
  
##### Add column indicating fusions containing known cancer genes
pizzly.fusions$fusions_cancer <- c(rep("-", nrow(pizzly.fusions)))

if ( nrow(pizzly.cancer_genes) > 0 ) {
  pizzly.fusions$fusions_cancer[ pizzly.fusions$geneA.name %in% pizzly.cancer_genes$geneA.name ] <- "Yes"
  pizzly.fusions$fusions_cancer[ pizzly.fusions$geneB.name %in% pizzly.cancer_genes$geneB.name ] <- "Yes"
}

##### Re-order fusion genes based on the reported fusions column
pizzly.fusions <- pizzly.fusions[ order(pizzly.fusions$reported_fusion, pizzly.fusions$splitcount, pizzly.fusions$paircount, pizzly.fusions$fusions_cancer, pizzly.fusions$reported_fusion_geneA, pizzly.fusions$reported_fusion_geneB, decreasing = TRUE), ]

##### Rename columns to match Arriba results
colnames(pizzly.fusions) <- gsub("geneA.name", "geneA", colnames(pizzly.fusions))
colnames(pizzly.fusions) <- gsub("geneB.name", "geneB", colnames(pizzly.fusions))
colnames(pizzly.fusions) <- gsub("paircount", "discordant_mates", colnames(pizzly.fusions))
colnames(pizzly.fusions) <- gsub("splitcount", "split_reads", colnames(pizzly.fusions))
pizzly.fusions <- pizzly.fusions[ colnames(pizzly.fusions) %!in% c("geneA.id", "geneB.id", "transcripts.list")]

##### Add results from Arriba
if ( runArribaChunk ) {
  
  ##### Add column with pizzly fusions
  pizzly.fusions$fusion <- paste(pizzly.fusions$geneA, pizzly.fusions$geneB, sep="__")
  fusions$Pizzly <-  c(rep("-", nrow(fusions)))
  
  ##### Loop through Pizzly results, mark fusions detected by both tools. For those detected only by pizzly adapt results format to Arriba results
  for ( i in 1:nrow(pizzly.fusions) ) {
    
    if ( !is.na(match(pizzly.fusions$fusion[i], paste(fusions$geneA, fusions$geneB, sep="__"))) ) {
      fusions$Pizzly[ match(pizzly.fusions$fusion[i], paste(fusions$geneA, fusions$geneB, sep="__")) ] <- "Yes"
      pizzly.fusions[ i, ] <-  rep("-", ncol(pizzly.fusions))
    } else {
      fusions <- rbind(fusions, data.frame(geneA=pizzly.fusions$geneA[i],geneB=pizzly.fusions$geneB[i], breakpointA="-", breakpointB="-", siteA="-", siteB="-", type="-", split_reads=pizzly.fusions$split_reads[i], split_readsA="-", split_readsB="-", discordant_mates=pizzly.fusions$discordant_mates[i], confidence="-", FGID=pizzly.fusions$FGID[i], reported_fusion=pizzly.fusions$reported_fusion[i], reported_fusion_geneA=pizzly.fusions$reported_fusion_geneA[i], reported_fusion_geneB=pizzly.fusions$reported_fusion_geneB[i], effector_gene=pizzly.fusions$effector_gene[i], fusions_cancer=pizzly.fusions$fusions_cancer[i], geneA_dna_support="-", geneB_dna_support="-", Arriba="-", Pizzly="Yes" ))
    }
  }
##### Otherwise add empty columns expected from Aribba results
} else {
  fusions <- pizzly.fusions
}

##### Add column to flag fusions supported by WGS data (from MANTA), if available
fusions$geneA_dna_support <- "-"
fusions$geneB_dna_support <- "-"

##### Clean the space and return output
rm(pizzly.fusions, pizzly.fusion.transcripts, pizzly.fusion.candidates, known_translocations.CGI, known_translocations.FusionGDB, pizzly.cancer_genes, pizzly.other_genes, trans.pairs)
##### Annotate fusion genes
##### Get data to annotate fusion genes
fusion_genes_annot <- ref_dataset.list[[dataset]][["gene_annot_all"]][ , c("ENSEMBL", "SYMBOL", "SEQNAME", "GENESEQSTART", "GENESEQEND") ]

fusions.annot <- fusions
fusions.annot$order <- 1:nrow(fusions.annot)

##### Get genomic info for fusions genes
fusion_annot1 <- merge(fusion_genes_annot, fusions.annot[ , c("order","geneA")], by = 2, sort=FALSE, all.y = TRUE)
fusion_annot1 <- fusion_annot1[ order(fusion_annot1$order), ]
fusion_annot2 <- merge(fusion_genes_annot, fusions.annot[ , c("order","geneB")], by = 2, sort=FALSE, all.y = TRUE)
fusion_annot2 <- fusion_annot2[ order(fusion_annot2$order), ]

##### Dragen + Arriba
if ( runDragenFusionChunk && runArribaChunk ) {
  fusion_annot <- cbind(fusion_annot1, fusion_annot2, fusions.annot[, c("score", "breakpointA", "breakpointB", "discordant_mates", "split_reads",  "split_readsA", "split_readsB", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB")])
  
##### Arriba / Arriba + Pizzly
} else if ( runArribaChunk ) {
  fusion_annot <- cbind(fusion_annot1, fusion_annot2, fusions.annot[, c("breakpointA", "breakpointB", "discordant_mates", "split_reads", "split_readsA", "split_readsB", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB")])
  
##### Dragen only
} else if ( runDragenFusionChunk ) {
  
  #####  Dragen's fusion format version 3.9.3
  if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(dragen.fusions)) ) {
    fusion_annot <- cbind(fusion_annot1, fusion_annot2, fusions.annot[, c("score", "breakpointA", "breakpointB", "split_reads", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB")])
    
  #####  Dragen's fusion format prior to version 3.9.3
  } else {
    fusion_annot <- cbind(fusion_annot1, fusion_annot2, fusions.annot[, c("score", "breakpointA", "breakpointB", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB")])
  }
  
##### Pizzly only
} else {
  fusion_annot <- cbind(fusion_annot1, fusion_annot2, fusions.annot[, c("split_reads", "discordant_mates", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB")])
}

##### Add column to flag fusions supported by WGS data (from MANTA), if available
fusion_annot$geneA_dna_support <- "-"
fusion_annot$geneB_dna_support <- "-"

colnames(fusion_annot) = make.names(colnames(fusion_annot), unique=TRUE)

##### Remove entries with missing annotation
fusion_annot <- fusion_annot[complete.cases(fusion_annot), ]

##### Clean the space
rm(fusion_annot1, fusion_annot2, fusions.annot, fusion_genes_annot)
##### Compare PIZZY and MANTA called gene fusion events
##### Add row for gene fusion events so that there is one row per gene
manta_sv <- ref_genes.list[["manta"]]
manta_sv$"Fusion genes" <- manta_sv$Gene

i <- 1
while ( i <= nrow(manta_sv) ) {
  if ( length(strsplit(manta_sv$Gene[i], split='&', fixed=TRUE)[[1]]) > 1 ) {
     
    ##### Insert new row for events involving two genes
    manta_sv <- tibble::add_row(manta_sv, .after = i)
    manta_sv[i+1, ] <- manta_sv[i, ]
    manta_sv$Gene[i] <- strsplit(manta_sv$Gene[i], split='&', fixed=TRUE)[[1]][1]
    manta_sv$Gene[i+1] <- strsplit(manta_sv$Gene[i+1], split='&', fixed=TRUE)[[1]][2]
    
    i <- i + 2
    
  } else {
    manta_sv$"Fusion genes"[i] <- ""
    i <- i + 1
  }
}

##### Compare fusion genes called by PIZZLy and MANTA
##### First limit MANTA output to fusions only
if ( runFusionChunk ) {
  manta_fusions <- unique(manta_sv[ grep("&", manta_sv$"Fusion genes"),  ]$Gene)
  manta_fusions <- manta_fusions[ manta_fusions %in% unique(c(as.vector(fusions$geneA), as.vector(fusions$geneB))) ]
    
  ##### Flag fusions that were also reported in MANTA
  if ( length(manta_fusions) > 0 ) {
    fusions$geneA_dna_support[ sort( match( manta_fusions , fusions$geneA ), na.last = NA ) ] <- "Yes"
    fusions$geneB_dna_support[ sort( match( manta_fusions , fusions$geneB ), na.last = NA ) ] <- "Yes"
      
    fusion_annot$geneA_dna_support[ sort( match( manta_fusions , fusion_annot$SYMBOL ), na.last = NA ) ] <- "Yes"
    fusion_annot$geneB_dna_support[ sort( match( manta_fusions , fusion_annot$SYMBOL.1 ), na.last = NA ) ] <- "Yes"
  
    ##### Re-order fusion dataframe with MANTA supporting fusions on top
    if ( runArribaChunk ) {
      idx <- order(fusions$geneA_dna_support, fusions$geneB_dna_support, fusions$Arriba, fusions$reported_fusion, decreasing = TRUE)
    } else {
      idx <- order(fusions$geneA_dna_support, fusions$geneB_dna_support, fusions$reported_fusion, decreasing = TRUE)
    }
    
    fusions <- fusions[ idx, ]
    fusion_annot <- fusion_annot[ idx, ]
  }
}

##### Remove entries with missing annotation
fusion_annot <- fusion_annot[complete.cases(fusion_annot), ]

##### Clean the space and return output
rm(manta_fusions)
##### Filter out fusions that are with < 2 split reads and < 2 pair reads and are not supported by genomic data, are not reported and don't involve cancer genes

##### Dragen + Arriba
if ( runDragenFusionChunk && runArribaChunk ) {
  fusions <- fusions %>% dplyr::filter( split_reads > 1 | discordant_mates > 1 | score > 0 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
  fusion_annot <- fusion_annot %>% dplyr::filter( split_reads > 1 | discordant_mates > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")

  ##### Arriba / Arriba + Pizzly
} else if ( runArribaChunk ) {
  fusions <- fusions %>% dplyr::filter( split_reads > 1 | discordant_mates > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
  fusion_annot <- fusion_annot %>% dplyr::filter( split_reads > 1 | discordant_mates > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
  
##### Dragen only
} else if ( runDragenFusionChunk ) {
  
  ##### For Dragen , this filtering is not changing the results. We'll review the "Score" value again once we start to regularly produce the RNAsum report for Dragen results
  #####  Dragen's fusion format version 3.9.3
  if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(fusions)) ) {
    fusions <- fusions %>% dplyr::filter( split_reads > 1 | score > 0 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
    fusion_annot <- fusion_annot %>% dplyr::filter( score > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
  
  #####  Dragen's fusion format prior to version 3.9.3
  } else {
    fusions <- fusions %>% dplyr::filter( score > 0 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
    fusion_annot <- fusion_annot %>% dplyr::filter( score > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
  }
  
##### Pizzly only
} else {
  fusions <- fusions %>% dplyr::filter(split_reads > 1 | discordant_mates > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
  fusion_annot <- fusion_annot %>% dplyr::filter(split_reads > 1 | discordant_mates > 1 | geneA_dna_support != "-" | geneB_dna_support != "-" | reported_fusion != "-" | fusions_cancer != "-")
}
##### Indicate which fusions have genomic coordinates and can be presented on circos plot
##### Take into account only reported fusions or those with both genes genes supported by DNA
if ( runSVsChunk ) {
  fusion_annot_top <- fusion_annot[ fusion_annot$reported_fusion == "Yes" | fusion_annot$geneA_dna_support == "Yes" | fusion_annot$geneB_dna_support == "Yes" , ]
} else {
  fusion_annot_top <- fusion_annot[ fusion_annot$reported_fusion == "Yes" , ]
}

fusions$circos <- "-"
fusions$circos[ paste(fusions$geneA, fusions$geneB, sep="-") %in% paste(fusion_annot_top$SYMBOL, fusion_annot_top$SYMBOL.1, sep="-") ] <- "Yes"
##### Extract data for Immunogram genes
data <- ref_dataset.list[[dataset]][["data_to_report"]]
data <- data[ rownames(data) %in% ref_genes.list[["genes_immune"]]$immunogram$SYMBOL, ]

##### Create lists with caulcuation results for each individual Cancer-Immunity Cycle (CIC) step
CIC.list <- vector("list", length(unique(ref_genes.list[["genes_immune"]]$immunogram$CIC)))
names(CIC.list) <- unique(ref_genes.list[["genes_immune"]]$immunogram$CIC)
  
##### Calculate average expression for each Cancer-Immunity Cycle (CIC) step
for ( cic_step in unique(ref_genes.list[["genes_immune"]]$immunogram$CIC) ) {
  
  genes <- ref_genes.list[["genes_immune"]]$immunogram$SYMBOL[ ref_genes.list[["genes_immune"]]$immunogram$CIC %in% cic_step ]
  data.sub <- data[ rownames(data) %in% genes, ]
  CIC.list[[cic_step]] <- colMeans(data.sub)
}

##### Conver the list into dataframe
ref_genes.list[["genes_immune"]]$immunogram.df <- t(data.frame(matrix(unlist(CIC.list), nrow=length(CIC.list), byrow=T),stringsAsFactors=FALSE))
colnames(ref_genes.list[["genes_immune"]]$immunogram.df) <- names(CIC.list)
rownames(ref_genes.list[["genes_immune"]]$immunogram.df) <- colnames(data.sub)
##### Summarise the reference cohorts samples
target <- ref_dataset.list[[dataset]][["sample_annot"]]
ref_ext_cancer <- table(target$Target)[names(table(target$Target))==ext_cancer_group]
ref_int_cancer <- table(target$Target)[names(table(target$Target))==int_cancer_group]

if ( !is.null(add_cancer_group) ) {
  ref_ext_cancer <- table(target$Target)[names(table(target$Target))==c(ext_cancer_group)] +  table(target$Target)[names(table(target$Target))==c(add_cancer_group)]
}
##### Update altered genes in ...
##### ...gene fusion section: Include only those which are DNA-supported (see Structural variants section) or reported in FusionGDB 
if ( runFusionChunk ) {
  ref_genes.list[["summary"]]$Fusion <- fusions[ fusions$reported_fusion == "Yes" | fusions$geneA_dna_support == "Yes" | fusions$geneB_dna_support == "Yes" , ]
  ref_genes.list[["summary"]]$Fusion <- unique(c(as.character(ref_genes.list[["summary"]]$Fusion$geneA), as.character(ref_genes.list[["summary"]]$Fusion$geneB)))
} else {
  ref_genes.list[["summary"]]$Fusion <- NULL
}

##### ...copy-number (CN) section: include only genes with CN values > 3 or < 0.5
if ( runPurpleChunk ) {
  
  #### Keep only genes with user-define CN values
  ref_genes.list[["summary"]]$CN <- ref_dataset.list[[dataset]][["expr_mut_cn_data"]]
  ref_genes.list[["summary"]]$CN <- as.character(ref_genes.list[["summary"]]$CN[ ref_genes.list[["summary"]]$CN$CN <= cn_bottom | ref_genes.list[["summary"]]$CN$CN >= cn_top,  ]$Gene)
}

##### ...immune markers section: include only genes with available annotation
if ( params$immunogram ) {
  ref_genes.list[["summary"]]$Immune <- unique(c(ref_genes.list[["genes_immune"]]$immunogram$SYMBOL, ref_genes.list[["genes_immune"]]$immune_markers$SYMBOL))
} else {
  ref_genes.list[["summary"]]$Immune <- unique(ref_genes.list[["genes_immune"]]$immune_markers$SYMBOL)
}
suppressMessages(library(plotly))

##### Prepare dataframe for Sunburst plot summarising all altered genes
alt_genes.all.list <- ref_genes.list[["summary"]]

##### Don't show cancer genes list (too long)
alt_genes.all.list$Cancer <- NULL

##### Note all altered genes
alt_genes.all <- sort(table(unlist(alt_genes.all.list)), decreasing = TRUE)

for ( alt in names(alt_genes.all.list) ) {

  ##### Add only alteration type which has at least one alteration detected
  if ( length(alt_genes.all[ names(alt_genes.all) %in% alt_genes.all.list[[ alt ]] ])  > 0 ) {
    alt_genes.all.list[[ alt ]] <- alt_genes.all[ names(alt_genes.all) %in% alt_genes.all.list[[ alt ]] ]
  } else {
    alt_genes.all.list[[ alt ]] <- NULL
  }
}

sunburst.all.df <- data.frame(ids = names(alt_genes.all.list),
  labels = names(alt_genes.all.list),
  parents = rep("", length(alt_genes.all.list)),
  values = as.numeric(lengths(alt_genes.all.list))/100,
  stringsAsFactors = FALSE
)

for ( alt in names(alt_genes.all.list) ) {
  
  ##### Add only alteration type which has at least one alteration detected
  if ( length(alt_genes.all[ names(alt_genes.all) %in% names(alt_genes.all.list[[ alt ]]) ])  > 0 ) {
      
    sunburst.all.df <- rbind( sunburst.all.df , data.frame(ids = paste( alt, names(alt_genes.all.list[[ alt ]]), sep = " - "), 
          labels = paste0("\t\t", names(alt_genes.all.list[[ alt ]]), "\t\t"),
          parents = rep( alt , length(alt_genes.all.list[[ alt ]])),
          values = as.numeric(alt_genes.all.list[[ alt ]])
          ) )
  }
}

sunburst_plot <- NULL
sunburst_plot[[1]] <- plot_ly(sunburst.all.df, ids = ~ids, labels = ~labels, parents = ~parents, values = ~values, type = 'sunburst', width = 600, height = 600)

##### Now include only Identify genes that appear in more then two lists
alt_genes.list <- alt_genes.all.list
alt_genes <- alt_genes.all[ alt_genes.all > 1 ]

for ( alt in names(alt_genes.list) ) {

  ##### Add only alteration type which has at least one alteration detected
  if ( length(alt_genes[ names(alt_genes) %in% names(alt_genes.list[[ alt ]]) ])  > 0 ) {
    alt_genes.list[[ alt ]] <- alt_genes[ names(alt_genes) %in% names(alt_genes.list[[ alt ]]) ]
  } else {
    alt_genes.list[[ alt ]] <- NULL
  }
}
  
sunburst.df <- data.frame(ids = names(alt_genes.list),
  labels = names(alt_genes.list),
  parents = rep("", length(alt_genes.list)),
  values = as.numeric(lengths(alt_genes.list))/100,
  stringsAsFactors = FALSE
)
  
for ( alt in names(alt_genes.list) ) {
  sunburst.df <- rbind( sunburst.df , data.frame(ids = paste( alt, names(alt_genes.list[[ alt ]]), sep = " - "), 
        labels = paste0("\t\t", names(alt_genes.list[[ alt ]]), "\t\t"),
        parents = rep( alt , length(alt_genes.list[[ alt ]])),
        values = as.numeric(alt_genes.list[[ alt ]])
        ) )
}

if ( nrow(sunburst.df) > 0 ) {
  sunburst_plot[[2]] <- plot_ly(sunburst.df, ids = ~ids, labels = ~labels, parents = ~parents, values = ~values, type = 'sunburst', width = 600, height = 600)
} else {
  sunburst_plot[[2]] <- NA
}

##### Create directory for the plots
summaryPlotsDir <- paste(results_dir, "summaryPlots", sep = "/")
if ( !file.exists(summaryPlotsDir) ) {
  dir.create(summaryPlotsDir, recursive=TRUE)
}
  
##### Save interactive plot as html file
saveWidgetFix(sunburst_plot[[1]], file = paste(summaryPlotsDir, "sunburst_plot_all.html", sep = "/"))

if ( !is.na(sunburst_plot[[2]]) ) {
  saveWidgetFix(sunburst_plot[[2]], file = paste(summaryPlotsDir, "sunburst_plot.html", sep = "/"))
}

##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())

##### Clean the space
rm(sunburst.all.df, sunburst.df)
##### Prepare dataframe for a table summarising all altered genes
##### Create lists with alterations detected in each gene
genes.list <- names(alt_genes.all)
summary_table.list <- vector("list", length(genes.list))
names(summary_table.list) <- genes.list
  
##### Go through all alterated genes and note the alterations types
for ( gene in names(alt_genes.all) ) {
  for ( alt in names(alt_genes.all.list) ) {
    if ( gene %in% names(alt_genes.all.list[[ alt ]])  ) {
      summary_table.list[[ gene ]] <- c( summary_table.list[[ gene ]], "Yes" )
    } else {
      summary_table.list[[ gene ]] <- c( summary_table.list[[ gene ]], "-" )
    }
  }
  
  ##### Add links to external resources
  ##### Provide link to VICC meta-knowledgebase ( https://search.cancervariants.org )
  summary_table.list[[ gene ]] <- c( summary_table.list[[ gene ]], paste0("<a href='https://search.cancervariants.org/#", gene, "' target='_blank'>VICC</a>"))
      
  ##### Provide link to OncoKB
  if ( gene %in% rownames(ref_genes.list[["genes_oncokb"]]) ) {
    if ( ref_genes.list[["genes_oncokb"]][  gene, "OncoKB"] == "Yes" ) {
          
      summary_table.list[[ gene ]][ length(summary_table.list[[ gene ]])] <- paste( summary_table.list[[ gene ]][ length(summary_table.list[[ gene ]])] , paste0("<a href='http://oncokb.org/#/gene/", gene, "' target='_blank'>OncoKB</a>"), sep = ", ")
    }
  }
      
  ##### Provide link to CIViC database druggable genes ( https://civicdb.org )
  if ( gene %in% caner_genes_annot.list[["civic_clin_evid"]]$gene ) {
    summary_table.list[[ gene ]][ length(summary_table.list[[ gene ]])] <- paste( summary_table.list[[ gene ]][ length(summary_table.list[[ gene ]])] , paste0("<a href='", unique(caner_genes_annot.list[["civic_clin_evid"]][ caner_genes_annot.list[["civic_clin_evid"]]$gene == gene , "gene_civic_url"]), "' target='_blank'>CIViC</a>"), sep = ", ")
  }
}

##### Convert the list into data frame
summary_table.df <- data.frame(matrix(unlist(summary_table.list), nrow=length(summary_table.list), byrow=T),stringsAsFactors=FALSE)

##### Add gene names and number of section in which individual genes are reported
summary_table.df <- cbind(names(summary_table.list), summary_table.df)
summary_table.df <- cbind(summary_table.df, as.numeric(alt_genes.all))
colnames(summary_table.df) <- c("Gene", names(alt_genes.all.list), "Resources", "Count")

##### Add GeneCards links
summary_table.df$Gene <- paste0("<a href='https://www.genecards.org/cgi-bin/carddisp.pl?gene=", summary_table.df$Gene, "' target='_blank'>", summary_table.df$Gene, "</a>")

##### Clean the space and return output
rm(summary_table.list)

Input data summary

Reference patient cohorts

The following reference patient cohorts were used for the analysis:

Input genes

Out of the 27021 input genes 16264 are used for analyses:

  • 16257 have reliably detected expression
  • 7 are not expressed but are of interest and are included in analyses
  • 10757 are either not expressed or their expression level is too low to be detected
  • 0 genes were ignored due to lack of HGNC-approved gene symbol

NOTE, the 10764 genes with no/low expression are indicated in BLANK cells with missing values in expression summary tables in Mutated genes, Structural variants, CN altered genes, Immune markers, HRD genes and Cancer genes sections.

Library size

Bar-plot illustrating library size for each sample.

library_size

Data filtering and transformation

The read count data were converted into CPMs using edgeR functions. Genes with low counts were filtered out. The data were log2-transformed.

The CPM of 1 (cut-off for removing low expressed genes) corresponds to 16 reads in sample with the lowest sequencing depth, and 63 reads in sample with the greatest sequencing depth. The plot below presents the relation between read counts and the corresponding CPM values in the patient data. The red vertical line indicates the threshold for filtering genes with low counts.

counts_vs_transformed

Plot(s) below present CPM data distribution before and after filtering genes with low counts.

data_transformation_nonfiltered

if ( params$filter ) {
  data_transformation_filtered
}

Data normalisation

During the sample preparation or sequencing process, external factors that are not of biological interest can affect the expression of individual samples. It is assumed that all samples should have a similar range and distribution of expression values. Normalisation for sample-specific effects is required to ensure that the expression distributions of each sample are similar across the entire experiment. Normalisation is performed using TMM method.

Box-plots below present CPM data for individual samples, coloured by sample groups, before and after TMM normalisation.

data_nonnormalised

if ( params$norm != "none" ) {
  data_normalised
}

Exploratory data analysis

The expression data produced by different studies are confounded by non-biological experimental variances that prevent direct comparison of samples from different studies. In order to minimise the variance caused by confounding factors limma removeBatchEffect method was used to adjust expression measurements for potential batch effects. In brief, the strategy is to consider the investigated sample and the 40 PAAD (UMCCR) samples as one batch (regardless of the investigated sample tissue origin) and 20 TEST (TCGA) samples (of any cancer type) as another batch. The objective is to remove as much data variation due to technical factors as possible.

Principal component analysis (PCA) was performed to reduce the dimensionality of data to visually assess similarities and differences between samples. This exploratory analysis facilitates identification of the key factors affecting the variability in the expression data.

  • PCA plot

Scatter-plots of the first 2 principal components (PCs) constituting the primary source of variation in the data before and after batch effects correction.

ref_dataset.list[[dataset]][["pca_combined_data_processed"]][[2]]
#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
ref_dataset.list[[dataset]][["pca_batch_effect_corrected"]][[2]]
#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
  • Scree-plot

Scree-plots presenting the fraction of total variance (y-axis) attributed to each PC (x-axis) before and after batch effects correction. The PCs are ordered by decreasing order of contribution to total variance.

ref_dataset.list[[dataset]][["pca_combined_data_processed"]][[3]]
#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
ref_dataset.list[[dataset]][["pca_batch_effect_corrected"]][[3]]
#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
  • RLE plot

The relative log expression (RLE) plot is a useful diagnostic tool to visualise the differences between the distributions of read counts across samples. It shows boxplot of the log-ratios of the gene-level read counts (y-axis) of each sample to those of a reference sample (defined as the median across the samples). Ideally, the distributions should be centered around the zero line and as tight as possible. Clear deviations indicate the need for normalisation and/or the presence of outlying samples.

ref_dataset.list[[dataset]][["rle_combined_data_processed"]]

if ( params$norm != "none" ) {
  ref_dataset.list[[dataset]][["rle_batch_effect_corrected"]]
}

##### Present the treatment timeline plot
treatment_timeline

Findings summary

Per-alteration plot

All altered genes are summarised in the plot below. The number next to each gene indicates the number of times it appears across the following report sections: Fusion genes (supported by genomic data or reported in FusionGDB), Immune markers or HRD genes. That number is also reflected by the width of corresponding branches. Click on the category of interest to expand corresponding branches. Genes within each category are ordered by the number of report sections in which they appear and then alphabetically.

NOTE, no genes are listed in more then one section.

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, "Findings summary")
mysql_populate_update <- paste0(mysql_populate_update, "Findings summary")

##### Present per-alteration findings summary sunburst plot for all altered genes
if ( !is.na(sunburst_plot[[2]]) ) {
  sunburst_plot[[2]]
} else {
  sunburst_plot[[1]]
}
#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
##### Present per-alteration findings summary sunburst plot for altered genes listed in at least two report sections
if ( !is.na(sunburst_plot[[2]]) ) {
  sunburst_plot[[1]]
}

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
rm(counts_vs_transformed, data_transformation_nonfiltered, data_transformation_filtered, data_nonnormalised, data_normalised, treatment_timeline, data.annot)

Per-gene table

Table summarising all altered genes listed across following report sections: Fusion genes (supported by genomic data or reported in FusionGDB), Immune markers or HRD genes. The Resources column contains links to databases that may provide additional source of evidence for the altered genes’ clinical significance. Genes ordered by the number of report sections they appear in (Count column) and then alphabetically.

NOTE, no genes are listed in more then one section.

##### Present per-gene findings summary table
findings.summary <- DT::datatable( data = summary_table.df, filter="none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrltip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, deferRender = TRUE, scrollY = "333px", scroller = TRUE), width = 800, height = 455, caption = htmltools::tags$caption( style = 'caption-side: top; text-align: left; color:grey; font-size:100%'), escape = FALSE) %>%
      DT::formatStyle( columns = colnames(summary_table.df), `font-size` = '12px', 'text-align' = 'center' ) %>%
      ##### Colour cells according to evidence level and trust rating
      DT::formatStyle(columns = colnames(summary_table.df)[c(2:ncol(summary_table.df)-2)], 
                      backgroundColor = DT::styleEqual(c("-", "Yes"), c("transparent", "black")), color = DT::styleEqual(c("-", "Yes"), c("black", "white")))

findings.summary
##### Create directory for tables
summaryTableDir <- paste(results_dir, "summaryTables", sep = "/")
if ( !file.exists(summaryTableDir) ) {
    dir.create(summaryTableDir, recursive=TRUE)
}

saveWidgetFix(widget=findings.summary, file=paste(summaryTableDir, "findings.summary.html", sep = "/"), selfcontained=TRUE)

##### Clean the space
rm(summary_table.df, findings.summary, sunburst_plot)

Mutated genes

mRNA expression levels of genes containing single nucleotide variants (SNVs) or insertions/deletions (indels), obtained from the PCGR report, in patient’s sample and their average mRNA expression in samples from cancer cohorts. NOTE, only PCGR tier 1-4 and non-coding splice region variants are reported.

Mutation data for this sample is NOT AVAILABLE.

- Summary table

Out of the 0 mutated genes 0 include tier 1-4 variants and 0 non-coding splice region variant. Of these, the expression of 0 was reliably measured in patient’s sample. The remaining 0 genes are either not expressed or their expression level is too low to be detected (indicated in BLANK cells with missing values).

Percentiles

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",Mutated genes")
mysql_populate_update <- paste0(mysql_populate_update, ",Mutated genes")

##### Generate expression summary table for mutated genes (based on PCGR report)
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]

##### Consider only genes with mutations calssified within user-defined tiers
genes <- ref_genes.list[["summary"]]$Mutated

##### Deal with no genes or when more than 10 genes are of interest
if ( length(genes) == 0 ) {
  genes <- NULL
  limit_genes <- FALSE
  genes_no <- 0
} else if ( length(genes) > params$top_genes ) {
  limit_genes <- TRUE
  genes_no <- params$top_genes
} else {
  limit_genes <- FALSE
  genes_no <- length(genes)
}

mut_genes.expr.perc <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], mut_annot = ref_genes.list[["pcgr"]][, c("SYMBOL", "TIER", "CONSEQUENCE", "VARIANT_CLASS", "AF_TUMOR", "GENOMIC_CHANGE", "PROTEIN_CHANGE")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "perc", scaling = scaling)

##### Present the expression summary table
mut_genes.expr.perc[[1]]

##### Save the expression table as html file
##### Create directory for tables
if ( params$save_tables ) {
  saveWidgetFix(widget=mut_genes.expr.perc[[1]], file=paste(exprTableDir, "mut_genes.expr.perc.html", sep = "/"), selfcontained=TRUE)
}
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each mutated gene. Variants’ tier, consequence, class and tumour allele freuqnecy (AF), as well as genomic and protein change are also provided based on information from PCGR report. In case of multiple varaints detected in single gene the variant with the lowest tier is reported and other potential consequences are listed in column CONSEQUENCE_OTHER. Genes are ordered by increasing variants TIER and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column.


Z-scores

mut_genes.expr.z <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], mut_annot = ref_genes.list[["pcgr"]][, c("SYMBOL", "TIER", "CONSEQUENCE", "VARIANT_CLASS", "AF_TUMOR", "GENOMIC_CHANGE", "PROTEIN_CHANGE")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "z", scaling = scaling)[[1]]

##### Present the expression summary table
mut_genes.expr.z

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=mut_genes.expr.z, file=paste(exprTableDir, "mut_genes.expr.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(mut_genes.expr.z)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each mutated gene. Variants’ tier, consequence, class and tumour allele freuqnecy (AF), as well as genomic and protein change are also provided based on information from PCGR report. In case of multiple varaints detected in single gene the variant with the lowest tier is reported and other potential consequences are listed in column CONSEQUENCE_OTHER. Genes are ordered by increasing variants TIER and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column.


- Expression profiles


Fusion genes

Fusion genes prioritisation

Fusion genes detected in transcriptome data are prioritised in the following order:

  1. Involvement of fusion gene(s) detected in genomic data (if Structural variants results are available)

  2. Detected in transcriptome data by Arriba tool

  3. Reported fusion event according to FusionGDB database

  4. Decreasing number of split reads

  5. Decreasing number of pair reads

  6. Involvement of cancer gene(s) (see Cancer genes section)

Fusion genes filtering

Fusion genes detected in transcriptome data are reported if at least one of the following criteria is met:

  1. Involvement of fusion gene(s) detected in genomic data (if Structural variants results are available)

  2. Reported fusion event according to FusionGDB database

  3. Involvement of cancer gene(s) (see Cancer genes section)

  4. Split reads > 1

  5. Pair reads > 1 and split reads > 1

- Summary

Out of the 2 fusion event(s) 0 involve DNA-supported fusion genes (see Structural variants section), 0 are reported in FusionGDB and 0 involve Cancer genes.


##### Create a nice table output (with dataTable)
if ( runFusionChunk ) {
  
  ##### Update MySQL commend to populate RNA-seq data portal
  mysql_populate <- paste0(mysql_populate, ",Fusion genes")
  mysql_populate_update <- paste0(mysql_populate_update, ",Fusion genes")
  
  fusions.table <- fusions
  fusions.table$geneA <- as.vector(fusions.table$geneA)
  fusions.table$geneB <- as.vector(fusions.table$geneB)
  
  ##### Provide link to FusionGDB
  for ( i in 1:nrow(fusions.table) ) {
      if ( fusions.table$reported_fusion[i] == "Yes" ) {
        fusions.table$geneA[i] <- paste0("<a href='https://ccsm.uth.edu/FusionGDB/gene_search_result.cgi?page=page&type=quick_search&quick_search=", fusions.table$FGID[i], "' target='_blank'>", fusions.table$geneA[i], "</a>")
  
        fusions.table$geneB[i] <- paste0("<a href='https://ccsm.uth.edu/FusionGDB/gene_search_result.cgi?page=page&type=quick_search&quick_search=", fusions.table$FGID[i], "' target='_blank'>", fusions.table$geneB[i], "</a>")
      }
  }
  
  ##### Dragen + Arriba
  if ( runDragenFusionChunk && runArribaChunk ) {
    fusions.table <- fusions.table[ , names(fusions.table) %!in% "FGID" ]
    fusions.table <- fusions.table[ , c("geneA", "geneB", "split_reads", "split_readsA", "split_readsB", "discordant_mates", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB", "confidence", "score", "breakpointA", "breakpointB", "siteA", "siteB", "type", "circos")]
  names(fusions.table) <- c("Gene A", "Gene B", "Split reads (Total)", "Split reads (A)", "Split reads (B)", "Pair reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)", "Fusion gene (A)", "Fusion gene (B)", "Confidence (Arriba)", "Score (Dragen)", "Breakpoint (A)", "Breakpoint (B)", "Site (A)", "Site (B)", "Type", "Genomic view")
  
  ##### Arriba / Arriba + Pizzly
  } else if ( runArribaChunk ) {
    fusions.table <- fusions.table[ , names(fusions.table) %!in% "FGID" ]
    fusions.table <- fusions.table[ , c("geneA", "geneB", "split_reads", "split_readsA", "split_readsB", "discordant_mates", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB", "confidence", "breakpointA", "breakpointB", "siteA", "siteB", "type", "circos")]
  names(fusions.table) <- c("Gene A", "Gene B", "Split reads (Total)", "Split reads (A)", "Split reads (B)", "Pair reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)", "Fusion gene (A)", "Fusion gene (B)", "Confidence (Arriba)", "Breakpoint (A)", "Breakpoint (B)", "Site (A)", "Site (B)", "Type", "Genomic view")
  
  ##### Dragen only
  } else if ( runDragenFusionChunk ) {
    
    #####  Dragen's fusion format version 3.9.3
    if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(dragen.fusions)) ) {
        fusions.table <- fusions.table[ , names(fusions.table) %!in% "FGID" ]
        fusions.table <- fusions.table[ , c("geneA", "geneB", "split_reads", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB", "score", "breakpointA", "breakpointB", "siteA", "siteB", "circos")]
      names(fusions.table) <- c("Gene A", "Gene B", "Split reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)", "Fusion gene (A)", "Fusion gene (B)", "Score", "Breakpoint (A)", "Breakpoint (B)", "Site (A)", "Site (B)", "Genomic view")
      
    #####  Dragen's fusion format prior to version 3.9.3
    } else {
      fusions.table <- fusions.table[ , names(fusions.table) %!in% "FGID" ]
        fusions.table <- fusions.table[ , c("geneA", "geneB", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB", "score", "breakpointA", "breakpointB", "circos")]
      names(fusions.table) <- c("Gene A", "Gene B", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)", "Fusion gene (A)", "Fusion gene (B)", "Score", "Breakpoint (A)", "Breakpoint (B)", "Genomic view")
    }
  
  ##### Pizzly only
  } else {
    fusions.table <- fusions.table[ , names(fusions.table) %!in% "FGID" ]
    fusions.table <- fusions.table[ , c("geneA", "geneB", "split_reads", "discordant_mates", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer", "reported_fusion_geneA", "reported_fusion_geneB", "circos")]
  names(fusions.table) <- c("Gene A", "Gene B", "Split reads", "Pair reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)", "Fusion gene (A)", "Fusion gene (B)", "Genomic view")
  }
  
  ##### Present gene fusion events in a table
  fusions.summary <- DT::datatable( data = fusions.table, filter = "none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrtip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, deferRender = TRUE, scrollY = "333px", scroller = TRUE), width = 800, height = 490, caption = htmltools::tags$caption(style = 'caption-side: top; text-align: left; color:grey; font-size:100% ;'), escape = FALSE) %>%
      DT::formatStyle( columns = names(fusions.table), `font-size` = '12px', 'text-align' = 'center' ) %>%
    
      ##### Highlight rows with fusions involving cancer genes (grey) or DNA support (from MANTA, orange)
      DT::formatStyle( columns = colnames(fusions.table) %in% "Cancer gene(s)", backgroundColor = DT::styleEqual(c("-", "Yes"), c('transparent', 'lightgrey')) ) %>%
      DT::formatStyle( columns = colnames(fusions.table) %in% "DNA support (A)", backgroundColor = DT::styleEqual(c("-", "Yes"), c('transparent', 'coral')) ) %>%
      DT::formatStyle( columns = colnames(fusions.table) %in% "DNA support (B)", backgroundColor = DT::styleEqual(c("-", "Yes"), c('transparent', 'coral')) ) %>%
      DT::formatStyle( columns = colnames(fusions.table) %in% "Reported fusion", backgroundColor = DT::styleEqual( c("-", "Yes"), c('transparent', 'lightgreen')) )
  
  fusions.summary
  
} else {
  
  ##### Create empty table
  fusions.table <- data.frame(matrix(ncol = 18, nrow = 0))
  
  names(fusions.table) <- c("Gene A", "Gene B", "Split reads", "Pair reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)", "Fusion gene (A)", "Fusion gene (B)", "Breakpoint (A)", "Breakpoint (B)", "Genomic view")

  ##### Present gene fusion events in a table
  fusions.summary <- DT::datatable( data = fusions.table, filter = "none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrtip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, deferRender = TRUE, scrollY = "333px", scroller = TRUE), width = 800, height = 490, caption = htmltools::tags$caption(style = 'caption-side: top; text-align: left; color:grey; font-size:100% ;'), escape = FALSE) %>%
      DT::formatStyle( columns = names(fusions.table), `font-size` = '12px', 'text-align' = 'center' )
  
  fusions.summary
}
##### Save the table as html file
if ( params$save_tables ) {
  
  ##### Create directory for tables
  fusionsTableDir <- paste(results_dir, "fusionsTables", sep = "/")
  if ( !file.exists(fusionsTableDir) ) {
          dir.create(fusionsTableDir, recursive=TRUE)
  }

  saveWidgetFix(widget=fusions.summary, file=paste(fusionsTableDir, "fusions.summary.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space and return output
rm(fusions.table, fusions.summary)
Table legend

Cells in RED indicate DNA-supported fusion genes (see Structural variants section), cells in GREEN indicate fusion events reported in FusionGDB, and cells in GREY indicate fusions containing Cancer genes. Gene fusions reported in FusionGDB are hyperlinked. Genes known to be involved in gene fusions are flagged based on information provided in FusionGDB and Cancer Genome Interpreter (CGI) databases. Breakpoint (A/B) - genomic coordinates of the breakpoints in gene A/B; Site (A/B) - location of the breakpoints in gene A/B; Type - type of event based on the orientation of the supporting reads and the coordinates of breakpoints

Fusion events are ordered by the following columns:

DNA support (A/B): DNA-supported fusion gene(s) (see Structural variants section)

Confidence level from Arriba tool

Reported fusion: fusion event reported in FusionGDB

Split count: the number of supporting split reads

Pair count: the number of supporting pair reads

Cancer gene(s): gene fusion events involving Cancer genes

Fusion gene (A/B): gene(s) known to be involved in tumorigenesis across cancer types based on FusionGDB and CGI databases


- Genomic view

0 DNA-supported fusion genes (see Structural variants section) and 0 fusions events reported in FusionGDB are presented in the genomic context. Red colour is used for links between positions of same chromosomes and blue for links between different chromosomes. The table at the bottom contains genomic coordingates of individual fusion genes sorted based on their genomic location.

NOTE: 0 of such fusions do not have genomic information available and are not presented on the circos plot (see Genomic view column in the - Summary table).

Genomic data for this sample is NOT AVAILABLE.

##### Keep only reported fusions or those with or cancer gene(s) involved
if ( runSVsChunk ) {
  fusion_annot_top <- fusion_annot[ fusion_annot$reported_fusion == "Yes" | fusion_annot$geneA_dna_support == "Yes" | fusion_annot$geneB_dna_support == "Yes" , ]
} else {
  fusion_annot_top <- fusion_annot[ fusion_annot$reported_fusion == "Yes" , ]
}

if ( nrow(fusion_annot_top) > 0 ) {
  
  ##### Create folder for fusion plots
  fusionsPlotDir <- paste(results_dir, "fusionsPlot", sep = "/")
    
  if ( !file.exists(fusionsPlotDir) ) {
    dir.create(fusionsPlotDir, recursive=TRUE)
  }
  
  ##### Prepare object for RCircos
  eval(parse( text=paste0("data(UCSC.HG", params$ucsc_genome_assembly, ".Human.CytoBandIdeogram)")))
  cyto.info <- eval(parse( text=paste0("UCSC.HG", params$ucsc_genome_assembly, ".Human.CytoBandIdeogram")))
    
  ##### Check if all driver genes are located in standard chromosomes
  fusion_annot_top <- fusion_annot_top[ paste0("chr", fusion_annot_top$SEQNAME) %in% cyto.info$Chromosome,  ]
  
  fusion_annot_top.circos.pairs <- fusion_annot_top[, c("SEQNAME", "GENESEQSTART", "GENESEQEND", "SYMBOL","SEQNAME.1", "GENESEQSTART.1", "GENESEQEND.1", "SYMBOL.1")]
  
  ##### Add "chr" to chromosome numbers
  fusion_annot_top.circos.pairs$SEQNAME <- paste0("chr", fusion_annot_top.circos.pairs$SEQNAME)
  fusion_annot_top.circos.pairs$SEQNAME.1 <- paste0("chr", fusion_annot_top.circos.pairs$SEQNAME.1)
  
  ##### Change column names
  names(fusion_annot_top.circos.pairs) <- gsub("SEQNAME", "Chromosome", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("GENESEQSTART", "chromStart", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("GENESEQEND", "chromEnd", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("SYMBOL", "Gene", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("Chromosome.1", "Chromosome", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("chromStart.1", "chromStart", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("chromEnd.1", "chromEnd", names(fusion_annot_top.circos.pairs))
  names(fusion_annot_top.circos.pairs) <- gsub("Gene.1", "Gene", names(fusion_annot_top.circos.pairs))
  
  ##### Remove entries with missing genomic coordinates
  fusion_annot_top.circos.pairs <- fusion_annot_top.circos.pairs[complete.cases(fusion_annot_top.circos.pairs), ]
  fusion_annot_top.circos <- rbind(fusion_annot_top.circos.pairs[, 1:4 ], fusion_annot_top.circos.pairs[, 5:8 ])
  fusion_annot_top.circos.pairs <- fusion_annot_top.circos.pairs[, colnames(fusion_annot_top.circos.pairs) %!in% c("Gene", "Gene.1") ]
  
  ##### Generate circos plot
  RCircos.Set.Core.Components( cyto.info=cyto.info, chr.exclude=NULL, tracks.inside=4, tracks.outside=0 )
  RCircos.Set.Plot.Area()  
  RCircos.Chromosome.Ideogram.Plot()
  RCircos.Gene.Connector.Plot(genomic.data = fusion_annot_top.circos, track.num = 1, side="in") 
  RCircos.Gene.Name.Plot(gene.data = fusion_annot_top.circos, name.col = 4, track.num = 2, side = "in")
  RCircos.Link.Plot(link.data = fusion_annot_top.circos.pairs, track.num=4, by.chromosome=TRUE, is.sorted=FALSE, lineWidth=rep(2, nrow(fusion_annot_top.circos.pairs)))
}
##### Generate circos plot representing gene fusion events. NOTE. Only fusions involving fusion genes supported by MANTA or reported fusions are presented
if ( nrow(fusion_annot_top) > 0 ) {
  
  ##### Save circos into a png file
  png( filename = paste(fusionsPlotDir, "circosPlot.png", sep="/"), width = 800, height = 800, units = "px", pointsize = 24 )
  RCircos.Set.Core.Components( cyto.info=cyto.info, chr.exclude=NULL, tracks.inside=4, tracks.outside=0 )
  RCircos.Set.Plot.Area()  
  RCircos.Chromosome.Ideogram.Plot()
  RCircos.Gene.Connector.Plot(genomic.data = fusion_annot_top.circos, track.num = 1, side="in") 
  RCircos.Gene.Name.Plot(gene.data = fusion_annot_top.circos, name.col = 4, track.num = 2, side = "in")
  RCircos.Link.Plot(link.data = fusion_annot_top.circos.pairs, track.num=4, by.chromosome=TRUE, is.sorted=FALSE, lineWidth=rep(2, nrow(fusion_annot_top.circos.pairs)))
  invisible(dev.off())
    
  ##### Clean the space
  rm(fusion_annot_top.circos, fusion_annot_top.circos.pairs)
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
  
} else {
  cat("None of the transcriptome-based fusion events have supporting evidence from DNA data or was previously reported.")
}
None of the transcriptome-based fusion events have supporting evidence from DNA data or was previously reported.
if ( nrow(fusion_annot_top) > 0 ) {
  
  ##### Clean the table for better presentation
  ##### Dragen + Arriba / Pizzly + Arriba
  if ( runDragenFusionChunk && runArribaChunk ) {
    fusion_annot_top.clean <- fusion_annot_top[, c("SYMBOL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "SYMBOL.1", "SEQNAME.1", "GENESEQSTART.1", "GENESEQEND.1", "breakpointA", "breakpointB", "split_reads", "split_readsA", "split_readsB", "discordant_mates", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer") ]
    
  ##### Dragen only
  } else if ( runDragenFusionChunk ) {
    
    #####  Dragen's fusion format version 3.9.3
    if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(dragen.fusions)) ) {
      fusion_annot_top.clean <- fusion_annot_top[, c("SYMBOL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "SYMBOL.1", "SEQNAME.1", "GENESEQSTART.1", "GENESEQEND.1", "breakpointA", "breakpointB", "split_reads", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer") ]
      
    #####  Dragen's fusion format prior to version 3.9.3
    } else {
      fusion_annot_top.clean <- fusion_annot_top[, c("SYMBOL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "SYMBOL.1", "SEQNAME.1", "GENESEQSTART.1", "GENESEQEND.1", "breakpointA", "breakpointB", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer") ]
    }
    
  ##### Pizzly only
  } else {
    fusion_annot_top.clean <- fusion_annot_top[, c("SYMBOL", "SEQNAME", "GENESEQSTART", "GENESEQEND", "SYMBOL.1", "SEQNAME.1", "GENESEQSTART.1", "GENESEQEND.1", "split_reads", "discordant_mates", "geneA_dna_support", "geneB_dna_support", "reported_fusion", "fusions_cancer") ]
  }
  
  ##### Order fusions based on the genomic location (chrom and start positions)
  chrOrder <-c((1:22),"X","Y","M")
  
  fusion_annot_top.clean$SEQNAME <- factor(fusion_annot_top.clean$SEQNAME, chrOrder, ordered=TRUE)
  fusion_annot_top.clean$SEQNAME.1 <- factor(fusion_annot_top.clean$SEQNAME.1, chrOrder, ordered=TRUE)
  fusion_annot_top.clean <- fusion_annot_top.clean[do.call(order, fusion_annot_top.clean[, c("SEQNAME", "SEQNAME.1", "GENESEQSTART", "GENESEQSTART.1")]), ]
  
  ##### Dragen + Arriba / Pizzly + Arriba
  if ( runDragenFusionChunk && runArribaChunk) {
    names(fusion_annot_top.clean) <- c("Gene A", "Chrom (A)", "Start (A)", "End (A)", "Gene B", "Chrom (B)", "Start (B)", "End (B)", "Breakpoint (A)", "Breakpoint (B)", "Split reads (Total)", "Split reads (A)", "Split reads (B)", "Pair reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)")
    
  ##### Dragen only
  } else if ( runDragenFusionChunk ) {
    
    #####  Dragen's fusion format version 3.9.3
    if ( all(c("GeneALocation", "GeneBLocation", "NumSplitReads","NumSoftClippedReads", "Score") %in% colnames(dragen.fusions)) ) {
      names(fusion_annot_top.clean) <- c("Gene A", "Chrom (A)", "Start (A)", "End (A)", "Gene B", "Chrom (B)", "Start (B)", "End (B)", "Breakpoint (A)", "Breakpoint (B)", "Split reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)")
      
    #####  Dragen's fusion format prior to version 3.9.3
    } else {
      names(fusion_annot_top.clean) <- c("Gene A", "Chrom (A)", "Start (A)", "End (A)", "Gene B", "Chrom (B)", "Start (B)", "End (B)", "Breakpoint (A)", "Breakpoint (B)", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)")
    }
    
  ##### Pizzly only
  } else {
    names(fusion_annot_top.clean) <- c("Gene A", "Chrom (A)", "Start (A)", "End (A)", "Gene B", "Chrom (B)", "Start (B)", "End (B)", "Split reads", "Pair reads", "DNA support (A)", "DNA support (B)", "Reported fusion", "Cancer gene(s)")
  }

  fusions.genomicView <- DT::datatable( data = fusion_annot_top.clean, filter="none", rownames = FALSE, extensions = c('Buttons','Scroller'), options = list(pageLength = 10, dom = 'Bfrtip', buttons = c('excel', 'csv', 'pdf','copy','colvis'), scrollX = TRUE, deferRender = TRUE, scrollY = "167px", scroller = TRUE), width = 800, height = 318,  escape = FALSE) %>%
      DT::formatStyle( columns = names(fusion_annot_top.clean), `font-size` = '12px', 'text-align' = 'center' ) %>%
    
      ##### Highlight rows with fusions involving cancer genes (grey)
      DT::formatStyle( columns = colnames(fusion_annot_top.clean) %in% "Cancer gene(s)", backgroundColor = DT::styleEqual(c("-", "Yes"), c('transparent', 'lightgrey')) ) %>%
      DT::formatStyle( columns = colnames(fusion_annot_top.clean) %in% "DNA support (A)", backgroundColor = DT::styleEqual(c("-", "Yes"), c('transparent', 'coral')) ) %>%
      DT::formatStyle( columns = colnames(fusion_annot_top.clean) %in% "DNA support (B)", backgroundColor = DT::styleEqual(c("-", "Yes"), c('transparent', 'coral')) ) %>%
    DT::formatStyle( columns = colnames(fusion_annot_top.clean) %in% "Reported fusion", backgroundColor = DT::styleEqual( c("-", "Yes"), c('transparent', 'lightgreen')) )

  fusions.genomicView
}

##### Clean the space
rm(fusion_annot_top.clean)
Table legend

Cells in RED indicate DNA-supported fusion genes (see Structural variants section), cells in GREEN indicate gene fusions reported in FusionGDB, and cells hihglighted in GREY indicate fusions containing Cancer genes. Genes known to be involved in gene fusions are flagged based on information provided in FusionGDB and Cancer Genome Interpreter (CGI) databases. Fusion events are ordered by genomic coordinates of Gene A and then Gene B. DNA support (gene A/B) - DNA-supported fusion gene(s) (see) Structural variants section); Reported fusion - fusion event reported in FusionGDB; Cancer gene(s) - gene fusion events involving Cancer genes

##### Save the table as html file
if ( nrow(fusion_annot_top) > 0 && params$save_tables ) {
  saveWidgetFix(widget=fusions.genomicView, file=paste(fusionsTableDir, "fusions.genomicView.html", sep = "/"), selfcontained=TRUE)  
}

##### Clean the space and return output
rm(fusions.genomicView)

- Top hits

Expression profiles for gene fusion events involving DNA-supported fusion genes (see Structural variants section), gene fusions reported in FusionGDB or Cancer genes, indicated in green, red and grey columns in the Fusion genes table, respectively, and with the highest Split count and Pair count values.

NOTE: the visualisation is available only for fusion genes detected by Arriba (see the - Summary table).

ATAD2-FBXO32

Fusion genes expression
mRNA expression levels of fusion genes detected in patient’s sample and their average mRNA expression (Z-score) in samples from cancer cohorts.
Plot legend

Distribution of percentile values (y-axis) as a function of expression levels (Z-scores, x-axis) of ATAD2 in patient’s sample (black dot) and other reference cancer cohort(s) (median value(s)).

Read counts

Bar-plot illustrating read counts for ATAD2 across all samples. The ATAD2 read count in patient’s sample is indicated by black bar.

Expression distribution patterns

Plot illustrating distribution of expression levels (Z-scores) of ATAD2 observed across all samples along with simulated normal and bimodal distributions. The ATAD2 expression level observed in patient’s sample is indicated by black dot in each distribution.


Plot legend

Distribution of percentile values (y-axis) as a function of expression levels (Z-scores, x-axis) of FBXO32 in patient’s sample (black dot) and other reference cancer cohort(s) (median value(s)).

Read counts

Bar-plot illustrating read counts for FBXO32 across all samples. The FBXO32 read count in patient’s sample is indicated by black bar.

Expression distribution patterns

Plot illustrating distribution of expression levels (Z-scores) of FBXO32 observed across all samples along with simulated normal and bimodal distributions. The FBXO32 expression level observed in patient’s sample is indicated by black dot in each distribution.


Summary table
Percentiles

Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The Diff (Patient vs TEST (TCGA) ) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each fusion gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, are also indicated. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA) ) column. TSG - tumour suppressor gene


Z-scores

Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each fusion gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, are also indicated. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column. TSG - tumour suppressor gene


NRIP1-AF127577.4;LINC02246

Fusion genes expression
mRNA expression levels of fusion genes detected in patient’s sample and their average mRNA expression (Z-score) in samples from cancer cohorts.
Plot legend

Distribution of percentile values (y-axis) as a function of expression levels (Z-scores, x-axis) of NRIP1 in patient’s sample (black dot) and other reference cancer cohort(s) (median value(s)).

Read counts

Bar-plot illustrating read counts for NRIP1 across all samples. The NRIP1 read count in patient’s sample is indicated by black bar.

Expression distribution patterns

Plot illustrating distribution of expression levels (Z-scores) of NRIP1 observed across all samples along with simulated normal and bimodal distributions. The NRIP1 expression level observed in patient’s sample is indicated by black dot in each distribution.


NOTE, expression data is not available for AF127577.4;LINC02246.

Summary table
Percentiles

Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The Diff (Patient vs TEST (TCGA) ) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each fusion gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, are also indicated. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA) ) column. TSG - tumour suppressor gene


Z-scores

Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each fusion gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, are also indicated. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column. TSG - tumour suppressor gene



Structural variants

mRNA expression levels of genes located within detected structural variants (SVs), obtained from Manta SV caller, in patient’s sample and their average mRNA expression in samples from cancer cohorts.

SVs information for this sample is NOT AVAILABLE

- Summary table

Out of the genes affected by SVs, the expression of 0 was reliably measured in patient’s sample. The remaining genes are either not expressed or their expression level is too low to be detected (indicated in BLANK cells with missing values).

Percentiles

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",Structural variants")
mysql_populate_update <- paste0(mysql_populate_update, ",Structural variants")

##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]

##### Consider only SVs with known genes and those in MANTA output for which the expression levels were measured
genes <- unique(manta_sv$Gene)
genes <- genes[ genes %in% ref_dataset.list[[dataset]][["gene_annot_all"]]$SYMBOL ]

##### Deal with no genes or when more than 10 genes are of interest
if ( length(genes) == 0 ) {
  genes <- NULL
  limit_genes <- FALSE
  genes_no <- 0
} else if ( length(genes) > params$top_genes ) {
  limit_genes <- TRUE
  genes_no <- params$top_genes
} else {
  limit_genes <- FALSE
  genes_no <- length(genes)
}

sv_genes.expr.perc <- exprTable( genes = genes, data = data, sv_data = manta_sv, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "perc", scaling = scaling)

##### Present the expression summary table
sv_genes.expr.perc[[1]]

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=sv_genes.expr.perc[[1]], file=paste(exprTableDir, "sv_genes.expr.perc.html", sep = "/"), selfcontained=TRUE)
}
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, are also indicated. Genes are ordered by increasing SV score and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) columns. TSG - tumour suppressor gene

Tier: SV priority score based on AstraZeneca simple_sv_annotation.py script; 1 = high and 4 = low priority


Z-scores

##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
sv_genes.expr.z <- exprTable( genes = genes, data = data, sv_data = manta_sv, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "z", scaling = scaling)[[1]]

##### Present the expression summary table
sv_genes.expr.z

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=sv_genes.expr.z, file=paste(exprTableDir, "sv_genes.expr.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(sv_genes.expr.z)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, are also indicated. Genes are ordered by increasing SV score and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column. TSG - tumour suppressor gene

Tier: SV priority score based on AstraZeneca simple_sv_annotation.py script; 1 = high and 4 = low priority


- Expression profiles


CN altered genes

Section overlaying the mRNA expression data with per-gene somatic copy-number (CN) data (from PURPLE), as well as SNVs/indels and SVs data, if available.

CN information for this sample is NOT AVAILABLE.

- Genomic view

genes with available CN data (y-axis) are presented in the genomic context (x-axis). 0 of them (indicated by various colours) are Cancer genes and are gained or lost . All other genes are marked in gray or black.

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",CN altered genes")
mysql_populate_update <- paste0(mysql_populate_update, ",CN altered genes")

##### Generate genomic view plot with per-gene CN values (y-axis) along chromosomal coordinates (x-axis)
suppressMessages(library(manhattanly))
suppressMessages(library(plotly))

data <- ref_dataset.list[[dataset]][["expr_mut_cn_data_all"]]
data.sub <- ref_dataset.list[[dataset]][["expr_mut_cn_data"]]

##### Add SNVs
if ( runPcgrChunk ) {
  data$Alterations <- as.character(data$Alterations)
  data.sub$Alterations <- as.character(data.sub$Alterations)
}

##### Add fusion genes
if ( runFusionChunk ) {
  
  ##### Change the alteration type to "fusion" for fusion genes
  data$Alterations[ data$Gene %in% fusions$geneA  ] <- paste0( data$Alterations[ data$Gene %in% fusions$geneA  ], "; Fusion")
  data.sub$Alterations[ data.sub$Gene %in% fusions$geneA  ] <- paste0( data.sub$Alterations[ data.sub$Gene %in% fusions$geneA  ], "; Fusion")
  data$Alterations[ data$Gene %in% fusions$geneB  ] <- paste0( data$Alterations[ data$Gene %in% fusions$geneB  ], "; Fusion")
  data.sub$Alterations[ data.sub$Gene %in% fusions$geneB  ] <- paste0( data.sub$Alterations[ data.sub$Gene %in% fusions$geneB  ], "; Fusion")
}
            
##### Add genes involved in SVs (if data available)
if ( runSVsChunk ) {
  
  ##### Change the alteration type to "fusion" for fusion genes
  data$Alterations[ data$Gene %in% unique(manta_sv$Gene)  ] <- paste0( data$Alterations[ data$Gene %in% unique(manta_sv$Gene)  ], "; SV")
  ##### Change the alteration type to "fusion" for fusion genes
  data.sub$Alterations[ data.sub$Gene %in% unique(manta_sv$Gene)  ] <- paste0( data.sub$Alterations[ data.sub$Gene %in% unique(manta_sv$Gene)  ], "; SV")
}

##### Remove altaration status "None" for gene which are not mutated but are involved in fusions or SVs
data$Alterations <- gsub( "None", "CN", data$Alterations)
data.sub$Alterations <- gsub( "None", "CN", data.sub$Alterations)

##### Prepare dataframe for manhattanly
##### Keep only genes for which both genes have gene symbol (and genomics location) available
data <- data[ data$Gene %in% ref_dataset.list[[dataset]][["gene_annot"]]$SYMBOL, ]
names(data)[match("CN", names(data))] <- "P"

##### Merge genes genomic coordinates info with their annotation and expression data
data.annot <- merge(data, ref_dataset.list[[dataset]][["gene_annot"]], by.x = "Gene", by.y = "SYMBOL", all.x = FALSE)
data.annot$SEQNAME <- as.numeric(data.annot$SEQNAME)
data.annot$GENESEQSTART <- as.numeric(data.annot$GENESEQSTART)
data.annot <- data.annot[ !is.na(data.annot$SEQNAME), ]

if ( nrow(data.annot) > 0 ) {
  
  ##### Get plot results first to extract x-axis coordinated to annotate genes of interest
  manhattanr.res <- manhattanr(x = data.annot, chr = "SEQNAME", bp = "GENESEQSTART", p = "P", snp = "Gene", gene = "Z_score_diff", annotation1 = "Perc_diff", annotation2 = "Alterations", logp = FALSE)
  
  ##### Restrict the results to the genes of interest
  manhattanr.res$data <- manhattanr.res$data[ manhattanr.res$data$Gene %in% data.sub$Gene, ]
  
  p <- manhattanly(x = data.annot, chr = "SEQNAME", bp = "GENESEQSTART", p = "P", snp = "Gene", gene = "Z_score_diff", annotation1 = "Perc_diff", annotation2 = "Alterations", suggestiveline = cn_top, genomewideline  = cn_bottom, suggestiveline_color = "gray", genomewideline_color = "gray", ylab = "CN value", showgrid = FALSE, title = "", logp = FALSE) %>%
    
    add_markers(y = manhattanr.res$data$P, x = manhattanr.res$data$pos, 
                name = manhattanr.res$data$Gene,
                text = paste0("Gene: ", manhattanr.res$data$Gene, "\nZ_score_diff: ", manhattanr.res$data$Z_score_diff, "\nPerc_diff: ", manhattanr.res$data$Perc_diff, "\nAlterations: ", manhattanr.res$data$Alterations, "\nchr: ", manhattanr.res$data$CHR),
                mode = 'markers',
                marker = list(size=10, symbol="circle"),
                color = manhattanr.res$data$Gene,
                showlegend = TRUE,
                legendtitle=TRUE, 
                inherit = FALSE) %>%
    
    add_annotations( data = manhattanr.res$data, text=~Gene,
                      x=~pos, xanchor="left",
                      y=~P, yanchor="top",
                      font = list(color = "Grey", size = 10),
                      legendtitle=TRUE,
                      showarrow=FALSE )
  
  ##### Create directory for the plots
  PlotDir <- paste(results_dir, "cn_genomic_view", sep = "/")
  if ( !file.exists(PlotDir) ) {
    dir.create(PlotDir, recursive=TRUE)
  }
  
  ##### Save interactive plot as html file
  saveWidgetFix(p, file = paste(PlotDir, "cn_genomic_view.html", sep = "/"))
  
  #### Clear plots to free up some memory
  if(!is.null(dev.list())) invisible(dev.off())
  
} else {
  cat("None of the genes of interest are affected by changes in CN.")
  p <- NULL
}

p

##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:manhattanly", unload=FALSE)
detach("package:plotly", unload=FALSE)

CN data distribution

CN information for this sample is NOT AVAILABLE.

##### Generate a histogram illustrating CN data distribution
suppressMessages(library(plotly))
cn_dist_plot

##### Create directory for the plots
PlotDir <- paste(results_dir, "cn_dist_plot", sep = "/")
if ( !file.exists(PlotDir) ) {
  dir.create(PlotDir, recursive=TRUE)
}

##### Save interactive plot as html file
saveWidgetFix(cn_dist_plot, file = paste(PlotDir, "cn_dist_plot.html", sep = "/"))
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())

- Expression vs CN

Scatterplot comparing the per-gene difference in mRNA expression of Cancer genes between patient’s sample and cancer individuals (y-axis), and CN values (x-axis, from PURPLE).

Percentiles

##### Generate scatterplot with per-gene expression values (y-axis), CN values (x-axis) and mutation status info (colours)
suppressMessages(library(plotly))
cn_genes <- data.sub$Gene

if ( runPcgrChunk && length(cn_genes) > 0 ) {
  mutCNexprPlot(data = data.sub, alt_data = TRUE, cn_bottom = cn_bottom, cn_top = cn_top, comp_cancer = comp_cancer_group, type = "perc", report_dir = results_dir)
  
} else if ( length(cn_genes) > 0) {
  data.sub <- data.sub[ data.sub$Gene %in% cn_genes, ]
  mutCNexprPlot(data = data.sub, alt_data = FALSE, cn_bottom = cn_bottom, cn_top = cn_top, comp_cancer = comp_cancer_group, type = "perc", report_dir = results_dir)
  
} else {
  cn_genes <- NULL
  cat("None of the genes of interest are affected by changes in CN.")
}

##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())

Z-scores

##### Generate scatterplot with per-gene expression values (y-axis), CN values (x-axis) and mutation status info (colours)
suppressMessages(library(plotly))

if ( runPcgrChunk && length(cn_genes) > 0 ) {
  data.sub <- data.sub[ data.sub$Gene %in% cn_genes, ]
  mutCNexprPlot(data = data.sub, alt_data = TRUE, cn_bottom = cn_bottom, cn_top = cn_top, comp_cancer = comp_cancer_group, type = "z", report_dir = results_dir)
  
} else if ( length(cn_genes) > 0) {
  data.sub <- data.sub[ data.sub$Gene %in% cn_genes, ]
  mutCNexprPlot(data = data.sub, alt_data = FALSE, cn_bottom = cn_bottom, cn_top = cn_top, comp_cancer = comp_cancer_group, type = "z", report_dir = results_dir)
  
} else {
  cn_genes <- NULL
  cat("None of the genes of interest are affected by changes in CN.")
}

##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())

- Summary table

Out of the genes within gained or lost regions 0 are Cancer genes. The expression of 0 of these genes was reliably measured in patient’s sample. The remaining 0 genes are either not expressed or their expression level is too low to be detected (indicated in BLANK cells with missing values).

Gains

Table summarising the mRNA expression values in cancer and patient samples for genes with CN values >= (NA) (gains), based on patient’s genomic data (from PURPLE), and mutation status if available (from PCGR).

Percentiles
##### Generate expression summary table for per-gene expression values CN values and mutation status info (colours)
##### Keep only genes within CN gains
cn_data <- ref_dataset.list[[dataset]][["expr_mut_cn_data"]]
cn_data <- cn_data[ cn_data$CN >= cn_top, ]
cn_data <- cn_data[, "CN", drop=FALSE]
genes_gains = as.character(cn_genes[ cn_genes %in% rownames(cn_data) ])

##### Deal with no genes
if ( length(genes_gains) == 0 ) {
  genes_gains <- NULL
  genes_gains_no <- 0
} else if ( length(genes_gains) > params$top_genes ) {
  genes_gains_no <- params$top_genes
} else {
  genes_gains_no <- length(genes_gains)
}

##### Get expression data
data <- ref_dataset.list[[dataset]][["data_to_report"]]

if ( runPcgrChunk && runPurpleChunk ) {
  cn_expr_genes.expr.gains.perc <- exprTable( genes = genes_gains, data = data, cn_data = cn_data, cn_decrease = TRUE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], mut_annot = ref_genes.list[["pcgr"]][, c("SYMBOL", "TIER", "CONSEQUENCE", "VARIANT_CLASS", "AF_TUMOR", "GENOMIC_CHANGE", "PROTEIN_CHANGE")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "perc", scaling = scaling)
  
##### Generate expression summary table for per-gene expression values and CN values
} else if ( runPurpleChunk ) {
  cn_expr_genes.expr.gains.perc <- exprTable( genes = genes_gains, data = data, cn_data = cn_data, cn_decrease = TRUE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "perc", scaling = scaling)
}
  
##### Present the expression, CN and mutation data summary table
cn_expr_genes.expr.gains.perc[[1]]

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=cn_expr_genes.expr.gains.perc[[1]], file=paste(exprTableDir, "cn_expr_genes.expr.gains.perc.html", sep = "/"), selfcontained=TRUE)
}
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each gene. The CN values based on patient’s genomic data are presented in Patient (CN) column with a horizontal blue bar indicating the CN value of each gene in the context of other genes. If mutation data is availbale, then the variants’ tier, consequence, class and tumour allele freuqnecy (AF), as well as genomic and protein change are also provided on right-hand side based on information from PCGR report (similar to Mutated genes section). Genes are ordered by Patient (CN) and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) columns. CN - copy-number


Z-scores
##### Generate expression summary table for per-gene expression values CN values and mutation status info (colours)
if ( runPcgrChunk && runPurpleChunk ) {
  cn_expr_genes.expr.gains.z <- exprTable( genes = genes_gains, data = data, cn_data = cn_data, cn_decrease = TRUE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], mut_annot = ref_genes.list[["pcgr"]][, c("SYMBOL", "TIER", "CONSEQUENCE", "VARIANT_CLASS", "AF_TUMOR", "GENOMIC_CHANGE", "PROTEIN_CHANGE")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "z", scaling = scaling)
  
##### Generate expression summary table for per-gene expression values and CN values
} else if ( runPurpleChunk ) {
  cn_expr_genes.expr.gains.z <- exprTable( genes = genes_gains, data = data, cn_data = cn_data, cn_decrease = TRUE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "z", scaling = scaling)
}
  
##### Present the expression, CN and mutation data summary table
cn_expr_genes.expr.gains.z[[1]]

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=cn_expr_genes.expr.gains.z[[1]], file=paste(exprTableDir, "cn_expr_genes.expr.gains.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(cn_expr_genes.expr.gains.z, cn_data)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each gene. The CN values based on patient’s genomic data are presented in Patient (CN) column with a horizontal blue bar indicating the CN value of each gene in the context of other genes. If mutation data is availbale, then the variants’ tier, consequence, class and tumour allele freuqnecy (AF), as well as genomic and protein change are also provided on right-hand side based on information from PCGR report (similar to Mutated genes section). Genes are ordered by Patient (CN) and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) columns. CN - copy-number


Losses

Table summarising the mRNA expression values in cancer and patient samples for genes with CN values =< (NA) (losses), based on patient’s genomic data (from PURPLE), and mutation status if available (from PCGR).

Percentiles
##### Generate expression summary table for per-gene expression values CN values and mutation status info (colours)
##### Keep only genes within CN losses
cn_data <- ref_dataset.list[[dataset]][["expr_mut_cn_data"]]
cn_data <- cn_data[ cn_data$CN <= cn_bottom, ]
cn_data <- cn_data[, "CN", drop=FALSE]
genes_losses = as.character(cn_genes[ cn_genes %in% rownames(cn_data) ])

##### Deal with no genes or when more than 10 genes are of interest
if ( length(genes_losses) == 0 ) {
  genes_losses <- NULL
  genes_losses_no <- 0
} else if ( length(genes_losses) > params$top_genes ) {
  genes_losses_no <- params$top_genes
} else {
  genes_losses_no <- length(genes_losses)
}
  
if ( genes_gains_no + genes_losses_no > params$top_genes ) {
  limit_genes <- TRUE
} else {
  limit_genes <- FALSE
}

##### Get expression data
data <- ref_dataset.list[[dataset]][["data_to_report"]]

if ( runPcgrChunk && runPurpleChunk ) {
  cn_expr_genes.expr.losses.perc <- exprTable( genes = genes_losses, data = data, cn_data = cn_data, cn_decrease = FALSE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], mut_annot = ref_genes.list[["pcgr"]][, c("SYMBOL", "TIER", "CONSEQUENCE", "VARIANT_CLASS", "AF_TUMOR", "GENOMIC_CHANGE", "PROTEIN_CHANGE")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "perc", scaling = scaling)
  
##### Generate expression summary table for per-gene expression values and CN values
} else if ( runPurpleChunk ) {
  cn_expr_genes.expr.losses.perc <- exprTable( genes = genes_losses, data = data, cn_data = cn_data, cn_decrease = FALSE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "perc", scaling = scaling)
}
  
##### Present the expression, CN and mutation data summary table
cn_expr_genes.expr.losses.perc[[1]]

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=cn_expr_genes.expr.losses.perc[[1]], file=paste(exprTableDir, "cn_expr_genes.expr.losses.perc.html", sep = "/"), selfcontained=TRUE)
}
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each gene. The CN values based on patient’s genomic data are presented in Patient (CN) column with a horizontal blue bar indicating the CN value of each gene in the context of other genes. If mutation data is availbale, then the variants’ tier, consequence, class and tumour allele freuqnecy (AF), as well as genomic and protein change are also provided on right-hand side based on information from PCGR report (similar to Mutated genes section). Genes are ordered by Patient (CN) and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) columns. CN - copy-number


Z-scores
##### Generate expression summary table for per-gene expression values CN values and mutation status info (colours)
if ( runPcgrChunk && runPurpleChunk ) {
  cn_expr_genes.expr.losses.z <- exprTable( genes = genes_losses, data = data, cn_data = cn_data, cn_decrease = FALSE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], mut_annot = ref_genes.list[["pcgr"]][, c("SYMBOL", "TIER", "CONSEQUENCE", "VARIANT_CLASS", "AF_TUMOR", "GENOMIC_CHANGE", "PROTEIN_CHANGE")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "z", scaling = scaling)
  
##### Generate expression summary table for per-gene expression values and CN values
} else if ( runPurpleChunk ) {
  cn_expr_genes.expr.losses.z <- exprTable( genes = genes_losses, data = data, cn_data = cn_data, cn_decrease = FALSE, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]][, c("Oncogene", "TSG", "Fusion", "Germline") ], ext_links = TRUE, type = "z", scaling = scaling)
}
  
##### Present the expression, CN and mutation data summary table
cn_expr_genes.expr.losses.z[[1]]

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=cn_expr_genes.expr.losses.z[[1]], file=paste(exprTableDir, "cn_expr_genes.expr.losses.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space and return output
rm(cn_data, cn_expr_genes.expr.losses.z)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each gene. The CN values based on patient’s genomic data are presented in Patient (CN) column with a horizontal blue bar indicating the CN value of each gene in the context of other genes. If mutation data is availbale, then the variants’ tier, consequence, class and tumour allele freuqnecy (AF), as well as genomic and protein change are also provided on right-hand side based on information from PCGR report (similar to Mutated genes section). Genes are ordered by Patient (CN) and then by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) columns. CN - copy-number


- Expression profiles

Gains


Losses


Immune markers

Section presenting expression levels of immune markers to assess pre-existing anti-cancer immunity and likelihood of response to immunotherapy. Their mRNA expression levels are presented in patient’s sample along their average mRNA expression in samples from cancer cohorts.

Out of the 70 immune markers the expression of 70 was reliably measured in patient’s sample. The remaining 0 genes are either not expressed or their expression level is too low to be detected (indicated in BLANK cells with missing values).

- Summary table

Percentiles

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",Immune markers")
mysql_populate_update <- paste0(mysql_populate_update, ",Immune markers")

##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]
genes <- unique(unlist(ref_genes.list[["genes_immune"]]$immune_markers$SYMBOL))

##### Deal with no genes or when more than 10 genes are of interest
if ( length(genes) == 0 ) {
  genes <- NULL
}

immune_genes.expr.perc <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL", "Immune_Cycle_Role")], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "perc", scaling = scaling)[[1]]

##### Present the expression summary table
immune_genes.expr.perc
##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=immune_genes.expr.perc, file=paste(exprTableDir, "immune_genes.expr.perc.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(immune_genes.expr.perc)
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each immune marker. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column.


Z-scores

##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
immune_genes.expr.z <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL", "Immune_Cycle_Role")], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "z", scaling = scaling)[[1]]

##### Present the expression summary table
immune_genes.expr.z
##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=immune_genes.expr.z, file=paste(exprTableDir, "immune_genes.expr.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(immune_genes.expr.z)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each immune marker. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column.


- Expression overview

Overview of immune markers expression profiles in patient’s sample and in samples from cancer patients.

Percentiles

suppressMessages(library(plotly))

##### Generate overview boxplot
if ( !is.null(genes) ) {
  glanceExprPlot(genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, hexcode = "immune_genes", type = "perc", sort = "alphabetically", scaling = scaling, report_dir = results_dir)
} else {
  cat("\nNo expression data is available for immune markers!\n")
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
Plot legend

The individual box(es) represent the TEST (TCGA) reference cancer cohort(s), and the BLACK dots indicate expression (percentile) values for each gene in the patient sample. Genes are ordered alphabetically.


Z-scores

suppressMessages(library(plotly))

##### Generate overview boxplot
if ( !is.null(genes) ) {
  glanceExprPlot(genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, hexcode = "immune_genes", type = "z", sort = "alphabetically", scaling = scaling, report_dir = results_dir)
} else {
  cat("\nNo expression data is available for immune markers!\n")
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
Plot legend

The individual box(es) represent the TEST (TCGA) reference cancer cohort(s), and the BLACK dots indicate expression (Z-score) values for each gene in the patient sample. Genes are ordered alphabetically.


##### Generate spider web plot to present the patient cancer immunity status. For more info about immunogram see the following papers
# https://www.sciencedirect.com/science/article/pii/S1556086417300084
# https://www.sciencedirect.com/science/article/pii/S1556086417302125
# https://www.europeanurology.com/article/S0302-2838(18)30685-7/fulltext?rss=yes
##### NOTE: currently, the mean expression (Z-score) values of genes from each of the 7 CIC steps are presented rather than the normalized enrichment scores (NES) from GSEA analysis performed for each geneset (CIC step)

##### Preset cancer immunity status for the patient using web-plot
webplot(as.data.frame(ref_genes.list[["genes_immune"]]$immunogram.df), data.row = ncol(data), main = "", add = FALSE, col = "black")

##### Now add data for samples with specific immunogram patterns, e.g. T-cell–rich, T-cell–poor, T-cell–intermediate...
#webplot(as.data.frame(ref_genes.list[["genes_immune"]]$immunogram.df), data.row = 5, main = "", add = TRUE, col = "powderblue", lty = 5)
#webplot(as.data.frame(ref_genes.list[["genes_immune"]]$immunogram.df), data.row = 156, main = "", add = TRUE, col = "forestgreen", lty = 5)
#webplot(as.data.frame(ref_genes.list[["genes_immune"]]$immunogram.df), data.row = 194, main = "", add = TRUE, col = "red", lty = 5)
#legend("topright", legend=c("Patient", "T-cell–rich","T-cell–poor", "T-cell–intermediate"), fill=c("black", "powderblue", "forestgreen", "red"), bty="n", bg = "transparent", cex = 0.8)

#### Clear plots to free up some memory
#if(!is.null(dev.list())) invisible(dev.off())
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]
genes <- unique(unlist(ref_genes.list[["genes_immune"]]$immunogram$SYMBOL))

##### Deal with no genes or when more than 10 genes are of interest
if ( length(genes) == 0 ) {
  genes <- NULL
}

##### Generate expression summary table for cancer genes from OncoKB and UMCCr (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
immunogram.expr.perc <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL", "CIC")], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "perc", scaling = scaling)[[1]]

##### Present the expression summary table
immunogram.expr.perc

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=immunogram.expr.perc, file=paste(exprTableDir, "immunogram.expr.perc.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(immunogram.expr.perc)
##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
immunogram.expr.z <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL", "CIC")], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "z", scaling = scaling)[[1]]

##### Present the expression summary table
immunogram.expr.z

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=immunogram.expr.z, file=paste(exprTableDir, "immunogram.expr.z.html", sep = "/"), selfcontained=TRUE)
}

HRD genes

Section presenting expression levels of homologous recombination deficiency (HRD) genes to assess how many of these demonstrate low expression, which may indicate potential promoter methylation events. Their mRNA expression levels are presented in patient’s sample along their average mRNA expression in samples from cancer cohorts.

Out of the 36 hrd genes the expression of 35 was reliably measured in patient’s sample. The remaining 1 genes are either not expressed or their expression level is too low to be detected (indicated in BLANK cells with missing values).

- Summary table

Percentiles

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",HRD genes")
mysql_populate_update <- paste0(mysql_populate_update, ",HRD genes")

##### Generate expression summary table for hrd genes from Richqrd
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]
genes <- unique(unlist(ref_genes.list[["genes_hrd"]]$SYMBOL))

##### Deal with no genes
if ( length(genes) == 0 ) {
  genes <- NULL
}

hrd_genes.expr.perc <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "perc", scaling = scaling)

##### Present the expression summary table
hrd_genes.expr.perc[[1]]
##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=hrd_genes.expr.perc[[1]], file=paste(exprTableDir, "hrd_genes.expr.perc.html", sep = "/"), selfcontained=TRUE)
}
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each HRD gene. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column. TSG - tumour suppressor gene


Z-scores

##### Generate expression summary table for hrd genes from Richard
hrd_genes.expr.z <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], ext_links = TRUE, type = "z", scaling = scaling)

##### Present the expression summary table
hrd_genes.expr.z[[1]]
##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=hrd_genes.expr.z[[1]], file=paste(exprTableDir, "hrd_genes.expr.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(hrd_genes.expr.z)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each HRD gene. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column.


- Expression overview

Overview of HRD genes expression profiles in patient’s sample and in samples from cancer patients.

Percentiles

suppressMessages(library(plotly))

##### Generate overview boxplot
if ( !is.null(genes) ) {
  glanceExprPlot(genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, hexcode = "hrd_genes", type = "perc", sort = "alphabetically", scaling = scaling, report_dir = results_dir)
} else {
  cat("\nNo expression data is available for HRD genes!\n")
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
Plot legend

The individual box(es) represent the TEST (TCGA) reference cancer cohort(s), and the BLACK dots indicate expression (percentile) values for each gene in the patient sample. Genes are ordered alphabetically.


Z-scores

suppressMessages(library(plotly))

##### Generate overview boxplot
if ( !is.null(genes) ) {
  glanceExprPlot(genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, hexcode = "hrd_genes", type = "z", sort = "alphabetically", scaling = scaling, report_dir = results_dir)
} else {
  cat("\nNo expression data is available for HRD genes!\n")
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
Plot legend

The individual box(es) represent the TEST (TCGA) reference cancer cohort(s), and the BLACK dots indicate expression (Z-score) values for each gene in the patient sample. Genes are ordered alphabetically.


Cancer genes

mRNA expression levels of cancer genes in patient’s sample and their average mRNA expression in samples from cancer cohorts. These include genes reported in the following gene panels/resources UMCCR cancer genes, OncoKB, MSK-IMPACT, MSK-HEME, Foundation One, Foundation One Heme, Vogelstein and Sanger Cancer Gene Census (CGC).

- Summary table

Out of the 1315 cancer genes the expression of 1280 was reliably measured in patient’s sample. The remaining 35 genes are either not expressed or their expression level is too low to be detected (indicated in BLANK cells with missing values).

Percentiles

##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",Cancer genes,All genes")
mysql_populate_update <- paste0(mysql_populate_update, ",Cancer genes,All genes")

##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
targets <- ref_dataset.list[[dataset]][["sample_annot"]]
data <- ref_dataset.list[[dataset]][["data_to_report"]]
genes <- rownames(ref_genes.list[["genes_cancer"]])

##### Deal with no genes
if ( length(genes) == 0 ) {
  genes <- NULL
}

cancer_genes.expr.perc <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]], ext_links = TRUE, type = "perc", scaling = scaling)

##### Present the expression summary table
cancer_genes.expr.perc[[1]]
##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=cancer_genes.expr.perc[[1]], file=paste(exprTableDir, "cancer_genes.expr.perc.html", sep = "/"), selfcontained=TRUE)
}
Table legend

The RED colour range indicate relatively high expression (percentile) values and BLUE colour range indicate relatively low expression (percentile) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between percentiles in patient sample and reference cancer cohort for each cancer gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, and inclusion in various sequencing panels are also indicated. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column. TSG - tumour suppressor gene


Z-scores

##### Generate expression summary table for cancer genes from OncoKB and UMCCR (https://github.com/vladsaveliev/NGS_Utils/blob/master/ngs_utils/reference_data/key_genes/umccr_cancer_genes.2019-03-20.tsv)
cancer_genes.expr.z <- exprTable( genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, genes_annot = ref_dataset.list[[dataset]][["gene_annot_all"]][, c("SYMBOL", "ENSEMBL")], oncokb_annot = ref_genes.list[["genes_oncokb"]], cancer_genes = ref_genes.list[["genes_cancer"]], ext_links = TRUE, type = "z", scaling = scaling)[[1]]

##### Present the expression summary table
cancer_genes.expr.z
##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=cancer_genes.expr.z, file=paste(exprTableDir, "cancer_genes.expr.z.html", sep = "/"), selfcontained=TRUE)
}

##### Clean the space
rm(cancer_genes.expr.z)
Table legend

The RED colour range indicate relatively high expression (Z-score) values and BLUE colour range indicate relatively low expression (Z-score) values in individual sample group. The BLANK cells with missing values indicate genes with no/low expression. The Diff (Patient vs TEST (TCGA)) column illustrates the difference between Z-scores in patient sample and reference cancer cohort for each cancer gene. Genes considered to be oncogenes or tumour suppressor genes, according to OncoKB database, and inclusion in various sequencing panels are also indicated. Genes are ordered by decreasing absolute values in the Diff (Patient vs TEST (TCGA)) column. TSG - tumour suppressor gene


- Expression overview

Overview of expression profiles of 50 altered cancer genes with the greatest difference in mRNA expression (percentile) values between patient’s sample and the average mRNA expression in samples from cancer patients.

Percentiles

suppressMessages(library(plotly))

##### Generate overview boxplot
genes <- cancer_genes.expr.perc[[2]]$SYMBOL[1:50]

if ( !is.null(genes) ) {
  glanceExprPlot(genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, hexcode = "cancer_genes", type = "perc", sort = "none", scaling = scaling, report_dir = results_dir)
} else {
  cat("\nNo expression data is available for cancer genes!\n")
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
Plot legend

The individual box(es) represent the TEST (TCGA) reference cancer cohort(s), and the BLACK dots indicate expression (percentile) values for each gene in the patient sample. Genes are ordered by decreasing absolute values in the Patient vs TEST (TCGA) comparison.


Z-scores

suppressMessages(library(plotly))

if ( !is.null(genes) ) {
  glanceExprPlot(genes = genes, data = data, targets = targets, sampleName = sample_name, ext_cancer = ext_cancer_group, int_cancer = int_cancer_group, comp_cancer = comp_cancer_group, add_cancer = add_cancer_group, hexcode = "cancer_genes", type = "z", sort = "none", scaling = scaling, report_dir = results_dir)
} else {
  cat("\nNo expression data is available for cancer genes!\n")
}
##### Detach plotly package. Otherwise it clashes with other graphics devices
detach("package:plotly", unload=FALSE)

#### Clear plots to free up some memory
if(!is.null(dev.list())) invisible(dev.off())
Plot legend

The individual box(es) represent the TEST (TCGA) reference cancer cohort(s), and the BLACK dots indicate expression (Z-score) values for each gene in the patient sample. Genes are ordered by decreasing absolute values in the Patient vs TEST (TCGA) comparison.


##### Create directory for tables
drugsTableDir <- paste(results_dir, "drugsTables", sep = "/")
if ( !file.exists(drugsTableDir) ) {
  dir.create(drugsTableDir, recursive=TRUE)
}
##### Generate table with drugs targeting mutated cancer genes
genes <- mut_genes.expr.perc[[2]]$SYMBOL

drugsTable.mut_genes <- civicDrugTable(genes, civic_var_summaries = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = "mutation")

if ( params$drugs ) {
  drugsTable.mut_genes[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.mut_genes[[1]], file=paste(drugsTableDir, "drugsTable.mut_genes.html", sep = "/"), selfcontained=TRUE)
}
##### Generate table with drugs targeting fusion genes
genesA <- as.vector(fusions[ fusion_annot$reported_fusion == "Yes" | fusion_annot$geneA_dna_support == "Yes" | fusion_annot$geneB_dna_support == "Yes", ]$geneA)
genesB <- as.vector(fusions[ fusion_annot$reported_fusion == "Yes" | fusion_annot$geneA_dna_support == "Yes" | fusion_annot$geneB_dna_support == "Yes", ]$geneB)

drugsTable.fusion_genes <- civicDrugTable(genes = unique(c(genesA, genesB)), civic_var_summaries  = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid  = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = "fusion")

if ( params$drugs ) {
  drugsTable.fusion_genes[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.fusion_genes[[1]], file=paste(drugsTableDir, "drugsTable.fusion_genes.html", sep = "/"), selfcontained=TRUE)
}
##### Generate table with drugs targeting dysregulated cancer genes
genes <- unique(manta_sv$Gene)

drugsTable.sv_genes <- civicDrugTable(genes, civic_var_summaries  = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid  = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = NULL)

if ( params$drugs ) {
  drugsTable.sv_genes[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.sv_genes[[1]], file=paste(drugsTableDir, "drugsTable.sv_genes.html", sep = "/"), selfcontained=TRUE)
}
##### Generate table with drugs targeting CN altered genes
genes <- cn_expr_genes.expr.gains.perc[[2]]$SYMBOL

drugsTable.CN_altered_genes_gains <- civicDrugTable(genes, civic_var_summaries  = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid  = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = "copy_gain")

if ( params$drugs ) {
  drugsTable.CN_altered_genes_gains[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.CN_altered_genes_gains[[1]], file=paste(drugsTableDir, "drugsTable.CN_altered_genes_gains.html", sep = "/"), selfcontained=TRUE)
}
##### Generate table with drugs targeting CN altered genes
genes <- cn_expr_genes.expr.losses.perc[[2]]$SYMBOL

drugsTable.CN_altered_genes_losses <- civicDrugTable(genes, civic_var_summaries  = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid  = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = "copy_loss")

if ( params$drugs ) {
  drugsTable.CN_altered_genes_losses[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.CN_altered_genes_losses[[1]], file=paste(drugsTableDir, "drugsTable.CN_altered_genes_losses.html", sep = "/"), selfcontained=TRUE)
}
##### Generate table with drugs targeting mutated cancer genes
genes <- hrd_genes.expr.perc[[2]]$SYMBOL[ hrd_genes.expr.perc[[2]]$SYMBOL %in% rownames(ref_dataset.list[[dataset]][["data_to_report"]]) ]

drugsTable.hrd_genes <- civicDrugTable(genes, civic_var_summaries = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = "mutation")

if ( params$drugs ) {
  drugsTable.hrd_genes[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.hrd_genes[[1]], file=paste(drugsTableDir, "drugsTable.hrd_genes.html", sep = "/"), selfcontained=TRUE)
}
##### Update MySQL commend to populate RNA-seq data portal
mysql_populate <- paste0(mysql_populate, ",Drug matching")
mysql_populate_update <- paste0(mysql_populate_update, ",Drug matching")

##### Generate table with drugs targeting dysregulated cancer genes
genes <- cancer_genes.expr.perc[[2]]$SYMBOL[1:50]

drugsTable.cancer_genes <- civicDrugTable(genes, civic_var_summaries  = caner_genes_annot.list[["civic_var_summaries"]], civic_clin_evid  = caner_genes_annot.list[["civic_clin_evid"]],  evid_type = "Predictive", var_type = "expression")

if ( params$drugs ) {
  drugsTable.cancer_genes[[1]]
}

##### Save the expression table as html file
if ( params$save_tables ) {
  saveWidgetFix(widget=drugsTable.cancer_genes[[1]], file=paste(drugsTableDir, "drugsTable.cancer_genes.html", sep = "/"), selfcontained=TRUE)
}
##### Finalise and write into a file the MySQL commend to populate RNA-seq data portal
##### Add input data info
mysql_populate <- paste0(mysql_populate, ",Input data")
mysql_populate_update <- paste0(mysql_populate_update, ",Input data")

##### Add clinical data if available
if (runClinicalChunk ) {
  mysql_populate <- paste0(mysql_populate, ",Clinical information")
  mysql_populate_update <- paste0(mysql_populate_update, ",Clinical information")
}

mysql_populate <- paste0(mysql_populate, "\", \"Transcriptome summary for sample ", sample_name, " generated on ", Sys.Date(), "\"", ", \"", Sys.Date(), "\" )")
mysql_populate_update <- paste0(mysql_populate_update, "\", Summary=\"Transcriptome summary for sample ", sample_name, " generated on ", Sys.Date(), "\"", ", Date=\"", Sys.Date(), "\";")
mysql_populate <- paste0(mysql_populate, "\n  ", mysql_populate_update, "\nSET @ID := 0;\nUPDATE RNAseq_reports SET ID = ( SELECT @ID := @ID + 1 );")
writeLines(mysql_populate, con = paste0(results_dir, "/", sample_name, ".RNAseq_report.sql"))

Addendum

Parameters

for ( i in 1:length(params) ) {
  cat(paste("Parameter: ", names(params)[i], "\nValue: ", paste(unlist(params[i]), collapse = ","), "\n\n", sep=""))
}
Parameter: annot_file
Value: genes/tx2gene.ensembl.v95.csv

Parameter: genes_cancer
Value: genes/umccr_cancer_genes.2019-03-20.tsv

Parameter: genes_immunogram
Value: genes/Genes_immunogram.txt

Parameter: genes_immune_markers
Value: genes/Genes_immune_markers.txt

Parameter: genes_hrd
Value: genes/Genes_HRD.txt

Parameter: oncokb_genes
Value: OncoKB/CancerGenesList.txt

Parameter: oncokb_clin_vars
Value: OncoKB/allActionableVariants.txt

Parameter: oncokb_all_vars
Value: OncoKB/allAnnotatedVariants.txt

Parameter: civic_var_summaries
Value: CIViC/01-Oct-2018-VariantSummaries.tsv

Parameter: civic_clin_evid
Value: CIViC/01-Oct-2018-ClinicalEvidenceSummaries.tsv

Parameter: cancer_biomarkers_trans
Value: cancer_biomarkers_database/cancer_genes_upon_trans.tsv

Parameter: FusionGDB
Value: FusionGDB/TCGA_ChiTaRS_combined_fusion_ORF_analyzed_gencode_h19v19_fgID.txt

Parameter: sample_name
Value: test_sample_WTS

Parameter: dataset
Value: TEST

Parameter: bcbio_rnaseq
Value: 

Parameter: dragen_rnaseq
Value: /Users/kanwals/UMCCR/git/RNAsum/data/test_data/stratus/test_sample_WTS_dragen_v3.9.3

Parameter: report_dir
Value: /Users/kanwals/UMCCR/git/RNAsum/rmd_files/../data/test_data/stratus/test_sample_WTS/RNAsum

Parameter: ref_data_dir
Value: ../data

Parameter: transform
Value: CPM

Parameter: norm
Value: TMM

Parameter: batch_rm
Value: TRUE

Parameter: filter
Value: TRUE

Parameter: log
Value: TRUE

Parameter: scaling
Value: gene-wise

Parameter: drugs
Value: FALSE

Parameter: immunogram
Value: FALSE

Parameter: umccrise
Value: 

Parameter: clinical_info
Value: NA

Parameter: clinical_id
Value: NA

Parameter: subject_id
Value: NA

Parameter: sample_source
Value: -

Parameter: dataset_name_incl
Value: 

Parameter: project
Value: -

Parameter: save_tables
Value: TRUE

Parameter: pcgr_tier
Value: 4

Parameter: pcgr_splice_vars
Value: TRUE

Parameter: cn_loss
Value: 5

Parameter: cn_gain
Value: 95

Parameter: top_genes
Value: 5

Parameter: hide_code_btn
Value: TRUE

Parameter: grch_version
Value: 38

Parameter: ensembl_version
Value: 86

Parameter: ucsc_genome_assembly
Value: 38
Reporter details

cat(paste0("The report was generated by \"", Sys.info()[ "user"], "\" using \"",  Sys.info()[ "nodename"], "\" node and \"",  Sys.info()[ "sysname"], "\" operating system."))
The report was generated by "kanwals" using "9999L-204093-M" node and "Darwin" operating system.
Session information

devtools::session_info()
─ Session info ───────────────────────────────────────────────────────────────
 setting  value                       
 version  R version 3.6.3 (2020-02-29)
 os       macOS  10.16                
 system   x86_64, darwin13.4.0        
 ui       unknown                     
 language (EN)                        
 collate  en_AU.UTF-8                 
 ctype    en_AU.UTF-8                 
 tz       Australia/Melbourne         
 date     2022-01-21                  

─ Packages ───────────────────────────────────────────────────────────────────
 package                     * version  date       lib source        
 annotate                      1.64.0   2019-10-29 [1] Bioconductor  
 AnnotationDbi               * 1.48.0   2019-10-29 [1] Bioconductor  
 AnnotationFilter            * 1.10.0   2019-10-29 [1] Bioconductor  
 aroma.light                   3.16.0   2019-10-29 [1] Bioconductor  
 askpass                       1.1      2019-01-13 [1] CRAN (R 3.6.3)
 assertthat                    0.2.1    2019-03-21 [1] CRAN (R 3.6.3)
 audio                         0.1-6    2019-03-19 [1] CRAN (R 3.6.1)
 backports                     1.2.1    2020-12-09 [1] CRAN (R 3.6.3)
 beepr                         1.3      2018-06-04 [1] CRAN (R 3.6.1)
 Biobase                     * 2.46.0   2019-10-29 [1] Bioconductor  
 BiocFileCache                 1.10.0   2019-10-29 [1] Bioconductor  
 BiocGenerics                * 0.32.0   2019-10-29 [1] Bioconductor  
 BiocParallel                * 1.20.0   2019-10-30 [1] Bioconductor  
 biomaRt                       2.42.0   2019-10-29 [1] Bioconductor  
 Biostrings                  * 2.54.0   2019-10-29 [1] Bioconductor  
 bit                           4.0.4    2020-08-04 [1] CRAN (R 3.6.3)
 bit64                         4.0.5    2020-08-30 [1] CRAN (R 3.6.3)
 bitops                        1.0-7    2021-04-24 [1] CRAN (R 3.6.3)
 blob                          1.2.1    2020-01-20 [1] CRAN (R 3.6.3)
 broom                         0.7.6    2021-04-05 [1] CRAN (R 3.6.3)
 BSgenome                    * 1.54.0   2019-10-29 [1] Bioconductor  
 BSgenome.Hsapiens.UCSC.hg38 * 1.4.1    2021-12-10 [1] Bioconductor  
 cachem                        1.0.5    2021-05-15 [1] CRAN (R 3.6.3)
 callr                         3.7.0    2021-04-20 [1] CRAN (R 3.6.3)
 cellranger                    1.1.0    2016-07-27 [1] CRAN (R 3.6.3)
 cli                           2.5.0    2021-04-26 [1] CRAN (R 3.6.3)
 colorspace                    2.0-1    2021-05-04 [1] CRAN (R 3.6.3)
 config                        0.3.1    2020-12-17 [1] CRAN (R 3.6.3)
 crayon                        1.4.1    2021-02-08 [1] CRAN (R 3.6.3)
 crosstalk                     1.1.1    2021-01-12 [1] CRAN (R 3.6.3)
 curl                          4.3.1    2021-04-30 [1] CRAN (R 3.6.3)
 data.table                    1.14.0   2021-02-21 [1] CRAN (R 3.6.3)
 DBI                           1.1.1    2021-01-15 [1] CRAN (R 3.6.3)
 dbplyr                        2.1.1    2021-04-06 [1] CRAN (R 3.6.3)
 DelayedArray                * 0.12.0   2019-10-29 [1] Bioconductor  
 desc                          1.3.0    2021-03-05 [1] CRAN (R 3.6.3)
 DESeq                         1.38.0   2019-10-29 [1] Bioconductor  
 devtools                    * 2.4.1    2021-05-05 [1] CRAN (R 3.6.3)
 digest                        0.6.27   2020-10-24 [1] CRAN (R 3.6.3)
 dplyr                       * 1.0.6    2021-05-05 [1] CRAN (R 3.6.3)
 DT                          * 0.18     2021-04-14 [1] CRAN (R 3.6.3)
 EDASeq                      * 2.20.0   2019-10-29 [1] Bioconductor  
 edgeR                       * 3.28.0   2019-10-29 [1] Bioconductor  
 ellipsis                      0.3.2    2021-04-29 [1] CRAN (R 3.6.3)
 EnsDb.Hsapiens.v86          * 2.99.0   2021-12-10 [1] Bioconductor  
 ensembldb                   * 2.10.0   2019-10-29 [1] Bioconductor  
 evaluate                      0.14     2019-05-28 [1] CRAN (R 3.6.3)
 fansi                         0.4.2    2021-01-15 [1] CRAN (R 3.6.3)
 farver                        2.1.0    2021-02-28 [1] CRAN (R 3.6.3)
 fastmap                       1.1.0    2021-01-25 [1] CRAN (R 3.6.3)
 forcats                     * 0.5.1    2021-01-27 [1] CRAN (R 3.6.3)
 forecast                      8.14     2021-03-11 [1] CRAN (R 3.6.3)
 fracdiff                      1.5-1    2020-01-24 [1] CRAN (R 3.6.3)
 fs                            1.5.0    2020-07-31 [1] CRAN (R 3.6.3)
 gargle                        1.1.0    2021-04-02 [1] CRAN (R 3.6.3)
 genefilter                    1.68.0   2019-10-29 [1] Bioconductor  
 geneplotter                   1.64.0   2019-10-29 [1] Bioconductor  
 generics                      0.1.0    2020-10-31 [1] CRAN (R 3.6.3)
 GenomeInfoDb                * 1.22.0   2019-10-29 [1] Bioconductor  
 GenomeInfoDbData              1.2.2    2021-12-10 [1] Bioconductor  
 GenomicAlignments           * 1.22.0   2019-10-29 [1] Bioconductor  
 GenomicFeatures             * 1.38.0   2019-10-29 [1] Bioconductor  
 GenomicRanges               * 1.38.0   2019-10-29 [1] Bioconductor  
 getopt                        1.20.3   2019-03-22 [1] CRAN (R 3.6.3)
 ggforce                     * 0.3.3    2021-03-05 [1] CRAN (R 3.6.3)
 ggplot2                     * 3.3.3    2020-12-30 [1] CRAN (R 3.6.3)
 glue                        * 1.4.2    2020-08-27 [1] CRAN (R 3.6.3)
 googleAuthR                   1.4.0    2021-04-02 [1] CRAN (R 3.6.3)
 googlesheets                  0.3.0    2019-10-11 [1] local         
 gridExtra                     2.3      2017-09-09 [1] CRAN (R 3.6.3)
 gtable                        0.3.0    2019-03-25 [1] CRAN (R 3.6.3)
 h2o                           3.26.0.2 2019-08-01 [1] CRAN (R 3.6.1)
 haven                         2.4.1    2021-04-23 [1] CRAN (R 3.6.3)
 highr                         0.9      2021-04-16 [1] CRAN (R 3.6.3)
 hms                           1.1.0    2021-05-17 [1] CRAN (R 3.6.3)
 htmltools                   * 0.5.1.1  2021-01-22 [1] CRAN (R 3.6.3)
 htmlwidgets                 * 1.5.3    2020-12-10 [1] CRAN (R 3.6.3)
 httr                          1.4.2    2020-07-20 [1] CRAN (R 3.6.3)
 hwriter                       1.3.2    2014-09-10 [1] CRAN (R 3.6.3)
 IRanges                     * 2.20.0   2019-10-29 [1] Bioconductor  
 jpeg                          0.1-8.1  2019-10-24 [1] CRAN (R 3.6.3)
 jsonlite                      1.7.2    2020-12-09 [1] CRAN (R 3.6.3)
 knitr                       * 1.33     2021-04-24 [1] CRAN (R 3.6.3)
 lares                       * 4.7      2019-10-11 [1] local         
 lattice                       0.20-44  2021-05-02 [1] CRAN (R 3.6.3)
 latticeExtra                  0.6-29   2019-12-19 [1] CRAN (R 3.6.3)
 lazyeval                      0.2.2    2019-03-15 [1] CRAN (R 3.6.3)
 lifecycle                     1.0.0    2021-02-15 [1] CRAN (R 3.6.3)
 limma                       * 3.42.0   2019-10-29 [1] Bioconductor  
 lmtest                        0.9-38   2020-09-09 [1] CRAN (R 3.6.3)
 locfit                        1.5-9.4  2020-03-25 [1] CRAN (R 3.6.3)
 lubridate                     1.7.10   2021-02-26 [1] CRAN (R 3.6.3)
 magrittr                      2.0.1    2020-11-17 [1] CRAN (R 3.6.3)
 MASS                          7.3-54   2021-05-03 [1] CRAN (R 3.6.3)
 Matrix                        1.3-3    2021-05-04 [1] CRAN (R 3.6.3)
 matrixStats                 * 0.58.0   2021-01-29 [1] CRAN (R 3.6.3)
 memoise                       2.0.0    2021-01-26 [1] CRAN (R 3.6.3)
 mice                          3.13.0   2021-01-27 [1] CRAN (R 3.6.3)
 modelr                        0.1.8    2020-05-19 [1] CRAN (R 3.6.3)
 munsell                       0.5.0    2018-06-12 [1] CRAN (R 3.6.3)
 nlme                          3.1-150  2020-10-24 [1] CRAN (R 3.6.3)
 NLP                           0.2-1    2020-10-14 [1] CRAN (R 3.6.3)
 nnet                          7.3-16   2021-05-03 [1] CRAN (R 3.6.3)
 openssl                       1.4.4    2021-04-30 [1] CRAN (R 3.6.3)
 openxlsx                    * 4.2.3    2020-10-27 [1] CRAN (R 3.6.3)
 optparse                    * 1.6.6    2020-04-16 [1] CRAN (R 3.6.3)
 pander                        0.6.3    2018-11-06 [1] CRAN (R 3.6.3)
 pdftools                    * 3.0.1    2021-05-06 [1] CRAN (R 3.6.3)
 pillar                        1.6.1    2021-05-16 [1] CRAN (R 3.6.3)
 pkgbuild                      1.2.0    2020-12-15 [1] CRAN (R 3.6.3)
 pkgconfig                     2.0.3    2019-09-22 [1] CRAN (R 3.6.3)
 pkgload                       1.2.1    2021-04-06 [1] CRAN (R 3.6.3)
 plotly                        4.9.0    2019-04-10 [1] CRAN (R 3.6.1)
 plyr                          1.8.6    2020-03-03 [1] CRAN (R 3.6.3)
 png                         * 0.1-7    2013-12-03 [1] CRAN (R 3.6.3)
 polyclip                      1.10-0   2019-03-14 [1] CRAN (R 3.6.3)
 preprocessCore              * 1.48.0   2019-10-29 [1] Bioconductor  
 prettyunits                   1.1.1    2020-01-24 [1] CRAN (R 3.6.3)
 pROC                          1.17.0.1 2021-01-13 [1] CRAN (R 3.6.3)
 processx                      3.5.2    2021-04-30 [1] CRAN (R 3.6.3)
 progress                      1.2.2    2019-05-16 [1] CRAN (R 3.6.3)
 ProtGenerics                  1.18.0   2019-10-29 [1] Bioconductor  
 ps                            1.6.0    2021-02-28 [1] CRAN (R 3.6.3)
 purrr                       * 0.3.4    2020-04-17 [1] CRAN (R 3.6.3)
 qpdf                          1.1      2019-03-07 [1] CRAN (R 3.6.3)
 quadprog                      1.5-8    2019-11-20 [1] CRAN (R 3.6.3)
 quantmod                      0.4.18   2020-12-09 [1] CRAN (R 3.6.3)
 R.methodsS3                   1.8.1    2020-08-26 [1] CRAN (R 3.6.3)
 R.oo                          1.24.0   2020-08-26 [1] CRAN (R 3.6.3)
 R.utils                       2.10.1   2020-08-26 [1] CRAN (R 3.6.3)
 R6                            2.5.0    2020-10-28 [1] CRAN (R 3.6.3)
 rappdirs                      0.3.3    2021-01-31 [1] CRAN (R 3.6.3)
 rapportools                 * 1.0      2014-01-07 [1] CRAN (R 3.6.3)
 RCircos                     * 1.2.1    2019-03-12 [1] CRAN (R 3.6.3)
 RColorBrewer                  1.1-2    2014-12-07 [1] CRAN (R 3.6.3)
 Rcpp                          1.0.6    2021-01-15 [1] CRAN (R 3.6.3)
 RCurl                         1.98-1.3 2021-03-16 [1] CRAN (R 3.6.3)
 rdrop2                        0.8.2.1  2020-08-05 [1] CRAN (R 3.6.3)
 readr                       * 1.4.0    2020-10-05 [1] CRAN (R 3.6.3)
 readxl                        1.3.1    2019-03-13 [1] CRAN (R 3.6.3)
 remotes                       2.3.0    2021-04-01 [1] CRAN (R 3.6.3)
 reprex                        2.0.0    2021-04-02 [1] CRAN (R 3.6.3)
 reshape                     * 0.8.8    2018-10-23 [1] CRAN (R 3.6.3)
 reshape2                      1.4.4    2020-04-09 [1] CRAN (R 3.6.3)
 rhdf5                       * 2.30.0   2019-10-29 [1] Bioconductor  
 Rhdf5lib                      1.8.0    2019-10-29 [1] Bioconductor  
 rlang                       * 0.4.11   2021-04-30 [1] CRAN (R 3.6.3)
 rlist                         0.4.6.1  2016-04-04 [1] CRAN (R 3.6.3)
 rmarkdown                     2.8      2021-05-07 [1] CRAN (R 3.6.3)
 rprojroot                     2.0.2    2020-11-15 [1] CRAN (R 3.6.3)
 Rsamtools                   * 2.2.0    2019-10-29 [1] Bioconductor  
 RSQLite                       2.2.5    2021-03-27 [1] CRAN (R 3.6.3)
 rstudioapi                    0.13     2020-11-12 [1] CRAN (R 3.6.3)
 rtracklayer                 * 1.46.0   2019-10-29 [1] Bioconductor  
 rvest                         1.0.0    2021-03-09 [1] CRAN (R 3.6.3)
 S4Vectors                   * 0.24.0   2019-10-29 [1] Bioconductor  
 scales                      * 1.1.1    2020-05-11 [1] CRAN (R 3.6.3)
 sessioninfo                   1.1.1    2018-11-05 [1] CRAN (R 3.6.3)
 ShortRead                   * 1.44.0   2019-10-29 [1] Bioconductor  
 slam                          0.1-48   2020-12-03 [1] CRAN (R 3.6.3)
 sp                            1.4-5    2021-01-10 [1] CRAN (R 3.6.3)
 stringi                       1.5.3    2020-09-09 [1] CRAN (R 3.6.3)
 stringr                     * 1.4.0    2019-02-10 [1] CRAN (R 3.6.3)
 SummarizedExperiment        * 1.16.0   2019-10-29 [1] Bioconductor  
 survival                      3.2-10   2021-03-16 [1] CRAN (R 3.6.3)
 testthat                      3.0.2    2021-02-14 [1] CRAN (R 3.6.3)
 tibble                      * 3.1.2    2021-05-16 [1] CRAN (R 3.6.3)
 tidyr                       * 1.1.3    2021-03-03 [1] CRAN (R 3.6.3)
 tidyselect                    1.1.1    2021-04-30 [1] CRAN (R 3.6.3)
 tidyverse                   * 1.3.1    2021-04-15 [1] CRAN (R 3.6.3)
 timeDate                      3043.102 2018-02-21 [1] CRAN (R 3.6.3)
 tm                            0.7-8    2020-11-18 [1] CRAN (R 3.6.3)
 tseries                       0.10-47  2019-06-05 [1] CRAN (R 3.6.3)
 TTR                           0.24.2   2020-09-01 [1] CRAN (R 3.6.3)
 tweenr                        1.0.2    2021-03-23 [1] CRAN (R 3.6.3)
 tximport                    * 1.14.0   2019-10-29 [1] Bioconductor  
 urca                          1.3-0    2016-09-06 [1] CRAN (R 3.6.3)
 usethis                     * 2.0.1    2021-02-10 [1] CRAN (R 3.6.3)
 utf8                          1.2.1    2021-03-12 [1] CRAN (R 3.6.3)
 vctrs                         0.3.8    2021-04-29 [1] CRAN (R 3.6.3)
 viridisLite                   0.4.0    2021-04-13 [1] CRAN (R 3.6.3)
 withr                         2.4.2    2021-04-18 [1] CRAN (R 3.6.3)
 wordcloud                     2.6      2018-08-24 [1] CRAN (R 3.6.3)
 xfun                          0.23     2021-05-15 [1] CRAN (R 3.6.3)
 XML                           3.99-0.3 2020-01-20 [1] CRAN (R 3.6.3)
 xml2                          1.3.2    2020-04-23 [1] CRAN (R 3.6.3)
 xtable                        1.8-4    2019-04-21 [1] CRAN (R 3.6.3)
 xts                           0.12-0   2020-01-19 [1] CRAN (R 3.6.3)
 XVector                     * 0.26.0   2019-10-29 [1] Bioconductor  
 yaml                          2.2.1    2020-02-01 [1] CRAN (R 3.6.3)
 zip                           2.1.1    2020-08-27 [1] CRAN (R 3.6.3)
 zlibbioc                      1.32.0   2019-10-29 [1] Bioconductor  
 zoo                           1.8-9    2021-03-09 [1] CRAN (R 3.6.3)

[1] /Users/kanwals/miniconda/envs/rnasum/lib/R/library
LS0tCnRpdGxlOiAnUGF0aWVudCBUcmFuc2NyaXB0b21lIFN1bW1hcnknCmF1dGhvcjogJ1VNQ0NSJwpkYXRlOiAnYHIgU3lzLkRhdGUoKWAnCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAga2VlcF9tZDogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRoZW1lOiByZWFkYWJsZQogICAgY3NzOiBSTkFzZXFfcmVwb3J0LmNzcwogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICBybWRmb3JtYXRzOjptYXRlcmlhbDoKICAgIGhpZ2hsaWdodDoga2F0ZQpwYXJhbXM6CiAgc2FtcGxlX25hbWU6ICd0ZXN0X3NhbXBsZV9XVFMnCiAgZGF0YXNldDogJ1RFU1QnCiAgYmNiaW9fcm5hc2VxOiAnLi4vZGF0YS90ZXN0X2RhdGEvZmluYWwvdGVzdF9zYW1wbGVfV1RTJwogIGRyYWdlbl9ybmFzZXE6IE5VTEwKICByZXBvcnRfZGlyOiAnLi4vZGF0YS90ZXN0X2RhdGEvZmluYWwvdGVzdF9zYW1wbGVfV1RTL1JOQXN1bScKICByZWZfZGF0YV9kaXI6ICcuLi9kYXRhJwogIHRyYW5zZm9ybTogJ0NQTScKICBmaWx0ZXI6IFRSVUUKICBub3JtOiAnVE1NJwogIGJhdGNoX3JtOiBUUlVFCiAgbG9nOiBUUlVFCiAgc2NhbGluZzogJ2dlbmUtd2lzZScKICBkcnVnczogRkFMU0UKICBpbW11bm9ncmFtOiBGQUxTRQogIHVtY2NyaXNlOiAnLi4vZGF0YS90ZXN0X2RhdGEvdW1jY3Jpc2VkL3Rlc3Rfc3ViamVjdF9fdGVzdF9zYW1wbGVfV0dTJwogIGNsaW5pY2FsX2luZm86ICcuLi9kYXRhL3Rlc3RfZGF0YS90ZXN0X2NsaW5pY2FsX2RhdGEueGxzeCcKICBjbGluaWNhbF9pZDogJ3Rlc3Rfc3ViamVjdCcKICBzdWJqZWN0X2lkOiAnJwogIHNhbXBsZV9zb3VyY2U6ICItIgogIGRhdGFzZXRfbmFtZV9pbmNsOiAnJwogIHByb2plY3Q6ICItIgogIHRvcF9nZW5lczogNQogIHNhdmVfdGFibGVzOiBUUlVFCiAgaGlkZV9jb2RlX2J0bjogVFJVRQogIHBjZ3JfdGllcjogNAogIHBjZ3Jfc3BsaWNlX3ZhcnM6IFRSVUUKICBjbl9sb3NzOiA1CiAgY25fZ2FpbjogOTUKICBncmNoX3ZlcnNpb246IDM4CiAgZW5zZW1ibF92ZXJzaW9uOiA4NgogIHVjc2NfZ2Vub21lX2Fzc2VtYmx5OiAzOAogIGFubm90X2ZpbGU6ICdnZW5lcy90eDJnZW5lLmVuc2VtYmwudjk1LmNzdicKICBnZW5lc19jYW5jZXI6ICdnZW5lcy91bWNjcl9jYW5jZXJfZ2VuZXMuMjAxOS0wMy0yMC50c3YnCiAgZ2VuZXNfaW1tdW5vZ3JhbTogJ2dlbmVzL0dlbmVzX2ltbXVub2dyYW0udHh0JwogIGdlbmVzX2ltbXVuZV9tYXJrZXJzOiAnZ2VuZXMvR2VuZXNfaW1tdW5lX21hcmtlcnMudHh0JwogIGdlbmVzX2hyZDogJ2dlbmVzL0dlbmVzX0hSRC50eHQnCiAgb25jb2tiX2dlbmVzOiAnT25jb0tCL0NhbmNlckdlbmVzTGlzdC50eHQnCiAgb25jb2tiX2NsaW5fdmFyczogJ09uY29LQi9hbGxBY3Rpb25hYmxlVmFyaWFudHMudHh0JwogIG9uY29rYl9hbGxfdmFyczogJ09uY29LQi9hbGxBbm5vdGF0ZWRWYXJpYW50cy50eHQnCiAgY2l2aWNfdmFyX3N1bW1hcmllczogJ0NJVmlDLzAxLU9jdC0yMDE4LVZhcmlhbnRTdW1tYXJpZXMudHN2JwogIGNpdmljX2NsaW5fZXZpZDogJ0NJVmlDLzAxLU9jdC0yMDE4LUNsaW5pY2FsRXZpZGVuY2VTdW1tYXJpZXMudHN2JwogIGNhbmNlcl9iaW9tYXJrZXJzX3RyYW5zOiAnY2FuY2VyX2Jpb21hcmtlcnNfZGF0YWJhc2UvY2FuY2VyX2dlbmVzX3Vwb25fdHJhbnMudHN2JwogIEZ1c2lvbkdEQjogJ0Z1c2lvbkdEQi9UQ0dBX0NoaVRhUlNfY29tYmluZWRfZnVzaW9uX09SRl9hbmFseXplZF9nZW5jb2RlX2gxOXYxOV9mZ0lELnR4dCcKLS0tCgpUcmFuc2NyaXB0b21lIHN1bW1hcnkgZm9yIHBhdGllbnQgc2FtcGxlICoqYHIgcGFzdGUwKHBhcmFtcyRzYW1wbGVfbmFtZSwgcGFyYW1zJGRhdGFzZXRfbmFtZV9pbmNsKWAqKi4KCgpgYGB7ciBzY3JpcHRfZGVzY3JpcHRpb24sIGV2YWw9RkFMU0V9CiMjIyMjIFdlIGF0dGVtcHQgdG8gc3RydWN0dXJlIHRoZSBzY3JpcHQgaW4gdGhlIGZvbGxvd2luZyB3YXk6CiMgMS4gRGVmaW5pbmcgZnVuY3Rpb25zCiMgMi4gTG9hZGluZyBsaWJyYXJpZXMKIyAzLiBMb2FkaW5nIHNhbXBsZSBkYXRhIGFuZCByZWZlcmVuY2UgZGF0YXNldHMKIyBUaGVuLi4uIGNvZGUgY2h1bmtzIGludm9sdmluZyBkYXRhIHByb2Nlc3NpbmcKIyBUaGVuLi4uIGNvZGUgY2h1bmtzIGNhbGxpbmcgdGhlIHByb2Nlc3NlZCBkYXRhIHRvIHByb2R1Y2UgdGFibGVzIC8gcGxvdHMgLyBkYXRhIHN1bW1hcnkKIyBGaW5pc2ggd2l0aCBTZXNzaW9uIGluZm8gaW4gQWRkZW5kdW0gc2VjdGlvbgoKIyMjIyMgVGhlIHByb2Nlc3NlZCBkYXRhIGlzIHN0b3JlZCBpbiAicmVmX2RhdGFzZXQubGlzdCIgbGlzdCB2YXJpYWJsZSB3aXRoIGVsZW1lbnRzIGhvbGRpbmcgdGhlIGZvbGxvd2luZyBkYXRhOgojIDEuIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGEiXV0gPSBjb21iaW5lZCByZWFkIGNvdW50IGRhdGEgKHJlZmVyZW5jZSBkYXRhc2V0cyArIHNhbXBsZSBkYXRhKSAoImNvbWJpbmVEYXRhc2V0cyIgZnVuY3Rpb24gb3V0cHV0IGluIHRoZSAibG9hZF9yZWZfZGF0YSBjaHVuayIpCiMgMi4gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sic2FtcGxlX2Fubm90Il1dID0gY29tYmluZWQgZGF0YSBzYW1wbGVzIGFubm90YXRpb24gKCJjb21iaW5lRGF0YXNldHMiIGZ1bmN0aW9uIG91dHB1dCBpbiB0aGUgImxvYWRfcmVmX2RhdGEgY2h1bmsiKQojIDMuIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNsaW5pY2FsX2luZm8iXV0gPSBjbGluaWNhbCBpbmZvcm1hdGlvbiAoc3Vydml2YWwgYW5kIHRyZWF0bWVudCBpbmZvKQojIDQuIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dID0gdHJhbnNmb3JtZWQsIGZpbHRlcmVkIGFuZCBub3JtYWxpc2VkIGRhdGEgKHNlZSAiZGF0YV90cmFuc2Zvcm1hdGlvbiIgYW5kICJkYXRhX25vcm1hbGlzYXRpb24iIGNodW5rcykKIyA1LiByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJiYXRjaF9lZmZlY3RfY29ycmVjdGVkIl1dID0gdHJhbnNmb3JtZWQsIGZpbHRlcmVkLCBub3JtYWxpc2VkIGFuZCBiYXRjaCBlZmZlY3QgY29ycmVjdGVkIGRhdGEgKHNlZSAiYmF0Y2hfZWZmZWN0X2NvcnJlY3Rpb24iIGNodW5rKQojIDYuIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInBjYV9jb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXSA9IFBDQSByZXN1bHRzIGZvciBjb21iaW5lZCBkYXRhCiMgNy4gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicGNhX2JhdGNoX2VmZmVjdF9jb3JyZWN0ZWQiXV0gPSBQQ0EgcmVzdWx0cyBmb3IgYmF0Y2gtZWZmZWN0IGNvcnJlY3RlZCBkYXRhCiMgOC4gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicmxlX2NvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dID0gUkxFIHBsb3QgZm9yIGNvbWJpbmVkIGRhdGEKIyA5LiByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJybGVfYmF0Y2hfZWZmZWN0X2NvcnJlY3RlZCJdXSA9IFJMRSBwbG90IGZvciBiYXRjaC1lZmZlY3QgY29ycmVjdGVkIGRhdGEKIyAxMC4gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0gPSBGdWxseSBjb21iaW5lZCBhbmQgcHJvY2Vzc2VkIGRhdGEgdG8gYmUgdXNlZCBmb3IgcmVwb3J0aW5nCiMgMTEuIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dID0gZ2VuZSBhbm5vdGF0aW9uIGZvciBjb21iaW5lZCByZWFkIGNvdW50IGRhdGEsIGNvbnRhaW5pbmcgYWxsIGlucHV0IGdlbmVzLiBUaGUgYW5ub3RhdGlvbiBpbmNsdWRlcyAiU1lNQk9MIiwgIkdFTkVCSU9UWVBFIiwgIkVOU0VNQkwiLCAiU0VRTkFNRSIsICJHRU5FU0VRU1RBUlQiLCAiR0VORVNFUUVORCIuICJFTlNFTUJMIiBpcyB1c2VkIGZvciByb3duYW1lcwojIDEyLiByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90Il1dID0gZ2VuZSBhbm5vdGF0aW9uIGZvciB0cmFuc2Zvcm1lZCwgZmlsdGVyZWQgYW5kIG5vcm1hbGlzZWQgZGF0YS4gVGhlIGFubm90YXRpb24gaW5jbHVkZXMgIlNZTUJPTCIsICJHRU5FQklPVFlQRSIsICJFTlNFTUJMIiwgIlNFUU5BTUUiLCAiR0VORVNFUVNUQVJUIiwgIkdFTkVTRVFFTkQiLiAiU1lNQk9MIiBpcyB1c2VkIGZvciByb3duYW1lcwojIDEzLiByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJleHByX211dF9jbl9kYXRhX2FsbCJdXSA9IGNvbWJpbmVkIGV4cHJlc3Npb24sIG11dGF0aW9uIGFuZCBjb3B5LW51bWJlciBkYXRhCiMgMTQuIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImV4cHJfbXV0X2NuX2RhdGEiXV0gPSBjb21iaW5lZCBleHByZXNzaW9uLCBtdXRhdGlvbiBhbmQgY29weS1udW1iZXIgZGF0YSBsaW1pdGVkIHRvIGNhbmNlciBnZW5lcyB0aGF0IG1lZXQgdXNlci1kZWluZmVkIENOIHZhbHVlcyB0aHJlc2hvbGQKCiMjIyMjIEdlbmVzIG9mIGludGVyZXN0IGFyZSBzdG9yZWQgaW4gInJlZl9nZW5lcy5saXN0IiBsaXN0IHZhcmlhYmxlIHdpdGggZWxlbWVudHMgaG9sZGluZyB0aGUgZm9sbG93aW5nIGdlbmUgc2V0czoKIyAxLiByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSA9IGxpc3Qgb2YgY2FuY2VyIGdlbmVzIGRlcml2ZWQgZnJvbSBVTUNDUiBDYW5jZXIgR2VuZSBsaXN0IChodHRwczovL2dpdGh1Yi5jb20vdmxhZHNhdmVsaWV2L05HU19VdGlscy9ibG9iL21hc3Rlci9uZ3NfdXRpbHMvcmVmZXJlbmNlX2RhdGEva2V5X2dlbmVzL3VtY2NyX2NhbmNlcl9nZW5lcy4yMDE5LTAzLTIwLnRzdikgYW5kIE9uY29LQiBwb3J0YWwgKGh0dHA6Ly9vbmNva2Iub3JnLyMvY2FuY2VyR2VuZXMpIAojIDIuIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dID0gbGlzdCBvZiBjYW5jZXIgZ2VuZXMgZGVyaXZlZCBmcm9tIE9uY29LQiBwb3J0YWwgKGh0dHA6Ly9vbmNva2Iub3JnLyMvY2FuY2VyR2VuZXMpIGFsb25lIChhbHRob3VnaCBnZW5lcyBwcmVzZW50IG9uIHRoZSBVTUNDUiBwYW5lbCBhcmUgYWxzbyBmbGFnZ2VkKQojIDMuIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dID0gbGlzdCBvZiBpbW11bmUgcmVwb25zZSBtYXJrZXJzIHByb3ZpZGVkIGluIHRoZSAiQW4gSW1tdW5vZ3JhbSBmb3IgdGhlIENhbmNlci1JbW11bml0eSBDeWNsZSIgcGFwZXIgYnkgS2FyYXNha2kgYXQgYWwgKDIwMTcpIChodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8yODA4ODUxMykgYW5kIE9tbmlTZXEgcmVwb3J0IChodHRwczovL3d3dy5vbW5pc2VxLmNvbS8pIAojIDQuIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaHJkIl1dID0gbGlzdCBvZiBocmQgKGhvbW9sb2dvdXMgcmVjb21iaW5hdGlvbiBkZWZpY2llbmN5KSBnZW5lcwojIDUuIHJlZl9nZW5lcy5saXN0W1sicGNnciJdXSA9IGxpc3QgYW5kIFBDR1IgYW5ub3RhdGlvbiBvZiBtdXRhdGVkIGdlbmVzIGluIGdpdmVuIHBhdGllbnQgYmFzZWQgb24gUENHUiByZXBvcnQKIyA2LiByZWZfZ2VuZXMubGlzdFtbInB1cnBsZSJdXSA9IGxpc3QgYW5kIFBVUlBMRSBhbm5vdGF0aW9uIG9mIGNvcHktbnVtYmVyIChDTikgYWx0ZXJlZCBnZW5lcyBpbiBnaXZlbiBwYXRpZW50IGJhc2VkIG9uIFBVUlBMRSByZXN1bHRzCiMgNy4gcmVmX2dlbmVzLmxpc3RbWyJtYW50YSJdXSA9IGxpc3QgYW5kIE1BTlRBIGFubm90YXRpb24gb2Ygc3RydWN0dXJhbCB2YXJpYW50cyAoU1ZzKSB3aXRoIGFmZmVjdGVkIGdlbmVzIGluIGdpdmVuIHBhdGllbnQgYmFzZWQgb24gTUFOVEEgcmVzdWx0cwojIDguIHJlZl9nZW5lcy5saXN0W1siYXJyaWJhIl1dID0gbGlzdCBhbmQgQVJSSUJBIGFubm90YXRpb24gb2YgZ2VuZSBmdXNpb24gZXZlbnRzIGRldGVjdGVkIGluIGdpdmVuIHBhdGllbnQgYmFzZWQgb24gQVJSSUJBIHJlc3VsdHMKIyA5LiByZWZfZ2VuZXMubGlzdFtbInBpenpseSJdXSA9IGxpc3QgYW5kIFBJWlpMWSBhbm5vdGF0aW9uIG9mIGdlbmUgZnVzaW9uIGV2ZW50cyBkZXRlY3RlZCBpbiBnaXZlbiBwYXRpZW50IGJhc2VkIG9uIFBJWlpMWSByZXN1bHRzCiMgMTAuIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSA9IHN1bW1hcnkgb2YgYWJvdmUtbWVudGlvbmVkIGdlbmUgbGlzdHMuIFRoZXNlIGdlbmUgbGlzdHMgYXJlIGFsc28gdXNlZCBmb3IgZ2VuZXJhdGluZyBleHByZXNzaW9uIHN1bW1hcnkgdGFibGVzIGFuZCBwbG90cyBpbiBpbmRpdmlkdWFsIHJlcG9ydCBzZWN0aW9ucwpgYGAKCmBgYHtyIGNvZGVfZGlzcGxheSwgZWNobyA9IEZBTFNFfQojIyMjIyBJbmNsdWRlIG9yIGV4Y2x1ZGUgdGhlICJDb2RlIiBidXR0b20gYWxsb3dpbmcgdG8gInNob3ciLyJoaWRlIiBjb2RlIGNodW5rcyBmcm9tIHRoZSByZXBvcnQgCmlmICggcGFyYW1zJGhpZGVfY29kZV9idG4gKSB7CiAgd3JpdGVMaW5lcygiLmJ0biB7IGRpc3BsYXk6IG5vbmUgOyIsIGNvbiA9ICJSTkFzZXFfcmVwb3J0LmNzcyIpCn0gZWxzZSB7CiAgd3JpdGVMaW5lcygiICIsIGNvbiA9ICJSTkFzZXFfcmVwb3J0LmNzcyIpCn0KYGBgCiAgCmBgYHtyIGNodW5rc190aW1pbmcsIGNvbW1lbnQ9TkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Ck5PVyA8LSBTeXMudGltZSgpCgojIyMjIyBUaW1lIGNodW5rcyBkdXJpbmcga25pdHRpbmcKa25pdHI6OmtuaXRfaG9va3Mkc2V0KHRpbWVpdCA9IGZ1bmN0aW9uKGJlZm9yZSkgewogIAogIGlmIChiZWZvcmUpIHsKICAgIHByaW50KHBhc3RlKCJTdGFydDoiLCBTeXMudGltZSgpKSkKICAgIE5PVyA8PC0gU3lzLnRpbWUoKQogIH0gZWxzZSB7CiAgICBwcmludChwYXN0ZSgiU3RvcDoiLCBTeXMudGltZSgpKSkKICAgIHByaW50KFN5cy50aW1lKCkgLSBOT1cpCiAgfQp9KQoKa25pdHI6Om9wdHNfY2h1bmskc2V0KHRpbWVpdCA9IFRSVUUpCmBgYAoKYGBge3IgZGVmaW5lX2Z1bmN0aW9ucywgY29tbWVudD1OQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyMjIyMgRGVmaW5lIGZ1bmN0aW9ucwojIyMjIyBDcmVhdGUgJ25vdCBpbicgb3BlcmF0b3IKIiUhaW4lIiA8LSBmdW5jdGlvbih4LHRhYmxlKSBtYXRjaCh4LHRhYmxlLCBub21hdGNoID0gMCkgPT0gMAoKIyMjIyMgUHJlcGFyZSBvYmplY3QgdG8gd3JpdGUgaW50byBhIGZpbGUKcHJlcGFyZTJ3cml0ZSA8LSBmdW5jdGlvbiAoeCkgewogIAogIHgyd3JpdGUgPC0gY2JpbmQocm93bmFtZXMoeCksIHgpCiAgY29sbmFtZXMoeDJ3cml0ZSkgPC0gYygiIixjb2xuYW1lcyh4KSkKICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UgYW5kIHJldHVybiBvdXRwdXQKICBybSh4KQogIHJldHVybih4MndyaXRlKQp9CgojIyMjIyBDb21iaW5lIHNhbXBsZSBleHByZXNzaW9uIHByb2ZpbGUgd2l0aCByZWZlcmVuY2UgZGF0YXNldHMuIFRoaXMgZnVuY3Rpb24gb3V0cHV0cyBhIHZlY3RvciB3aXRoIGZpcnN0IGVsZW1lbnQgY29udGFpbmluZyB0aGUgbWVyZ2VkIGRhdGEgYW5kIHNlY29uZCBlbGVtZW50IGNvbnRhaW5pbmcgbWVyZ2VkIHRhcmdldHMgaW5mbwpjb21iaW5lRGF0YXNldHMgPC0gZnVuY3Rpb24oc2FtcGxlX25hbWUsIHNhbXBsZV9jb3VudHMsIHJlZl9kYXRhLCByZXBvcnRfZGlyLCBkYXRhc2V0KSB7CiAgCiAgIyMjIyMgRXh0cmFjdCBpbmZvIGFib3V0IHRhcmdldCBmaWxlIGZvciB0aGUgZXh0ZXJuYWwgcmVmZXJlbmNlIGRhdGFzZXQKICB0YXJnZXQuZXh0IDwtIHJlYWQudGFibGUocmVmX2RhdGFbWyJleHRfcmVmIl1dWzJdLCBzZXA9Ilx0IiwgYXMuaXM9VFJVRSwgaGVhZGVyPVRSVUUpCiAgdGFyZ2V0LmV4dCA8LSBjYmluZCh0YXJnZXQuZXh0LCByZXAocmVmX2RhdGFbWyJleHRfcmVmIl1dWzNdLCBucm93KHRhcmdldC5leHQpKSkKICBjb2xuYW1lcyh0YXJnZXQuZXh0KVtuY29sKHRhcmdldC5leHQpXSA8LSAiRGF0YXNldCIKICAKICAjIyMjIyBBZGQgcHJleGl0IHRvIHNhbXBsZSBuYW1lcwogIHJvd25hbWVzKHRhcmdldC5leHQpIDwtIHBhc3RlKHRhcmdldC5leHRbLCJEYXRhc2V0Il0sIHRhcmdldC5leHRbLCJTYW1wbGVfbmFtZSJdLCBzZXAgPSAiLiIpCiAgdGFyZ2V0LmV4dCA8LSB0YXJnZXQuZXh0WywgLTFdCiAgCiAgIyMjIyMgRXh0cmFjdCBpbmZvIGFib3V0IHRhcmdldCBmaWxlIGZvciB0aGUgaW50ZXJuYWwgcmVmZXJlbmNlIGRhdGFzZXQKICB0YXJnZXQuaW50IDwtIHJlYWQudGFibGUocmVmX2RhdGFbWyJpbnRfcmVmIl1dWzJdLCBzZXA9Ilx0IiwgYXMuaXM9VFJVRSwgaGVhZGVyPVRSVUUpCiAgdGFyZ2V0LmludCA8LSBjYmluZCh0YXJnZXQuaW50LCByZXAocmVmX2RhdGFbWyJpbnRfcmVmIl1dWzNdLCBucm93KHRhcmdldC5pbnQpKSkKICBjb2xuYW1lcyh0YXJnZXQuaW50KVtuY29sKHRhcmdldC5pbnQpXSA8LSAiRGF0YXNldCIKICAgICAgCiAgIyMjIyMgQWRkIHByZXhpdCB0byBzYW1wbGUgbmFtZXMKICByb3duYW1lcyh0YXJnZXQuaW50KSA8LSBwYXN0ZSh0YXJnZXQuaW50WywiRGF0YXNldCJdLCB0YXJnZXQuaW50WywiU2FtcGxlX25hbWUiXSwgc2VwID0gIi4iKQogIHRhcmdldC5pbnQgPC0gdGFyZ2V0LmludFssIC0xXQogICAgICAKICB0YXJnZXQuY29tYiA8LSByYmluZCh0YXJnZXQuZXh0LCB0YXJnZXQuaW50KQogIAogICMjIyMjIEFkZCBzYW1wbGUgaW5mbwogIHRhcmdldC5zYW1wbGUgPC0gZGF0YS5mcmFtZShzYW1wbGVfbmFtZSwgc2FtcGxlX25hbWUpCiAgbmFtZXModGFyZ2V0LnNhbXBsZSkgPC0gbmFtZXModGFyZ2V0LmNvbWIpCiAgcm93bmFtZXModGFyZ2V0LnNhbXBsZSkgPC0gc2FtcGxlX25hbWUKICB0YXJnZXQuY29tYiA8LSByYmluZCggdGFyZ2V0LmNvbWIsIHRhcmdldC5zYW1wbGUgKQogIAogICMjIyMjIE1ha2Ugc3ludGFjdGljYWxseSB2YWxpZCBuYW1lcwogIHJvd25hbWVzKHRhcmdldC5jb21iKSA8LSBtYWtlLm5hbWVzKHJvd25hbWVzKHRhcmdldC5jb21iKSkKICAKICAjIyMjIyBSZWFkIHNhbXBsZSByZWFkIGNvdW50IGZpbGUgYW5kIGNvbWJpbmUgaXQgd2l0aCByZWZlcmVuY2UgZGF0YXNldHMKICBkYXRhc2V0cy5jb21iIDwtIHNhbXBsZV9jb3VudHMKICBuYW1lcyhkYXRhc2V0cy5jb21iKSA8LSBjKCIiLCBzYW1wbGVfbmFtZSkKICAgICAgCiAgIyMjIyMgbGlzdCBnZW5lcyBwcmVzZW50IGluIHRoZSBzYW1wbGUgcmVhZCBjb3VudCBmaWxlCiAgZ2VuZV9saXN0IDwtIGFzLnZlY3RvcihkYXRhc2V0cy5jb21iWywxXSkKICAgICAgCiAgIyMjIyMgTG9vcCB0aHJvdWdoIHRoZSBleHByZXNzaW9uIGRhdGEgZnJvbSBkaWZmZXJlbnQgZGF0YXNldHMgYW5kIG1lcmdlIHRoZW0gaW50byBvbmUgbWF0cml4CiAgZm9yICggaSBpbiAxOmxlbmd0aChyZWZfZGF0YSkgKSB7CiAgICAKICAgIGRhdGFzZXQuY291bnRzIDwtIGFzLmRhdGEuZnJhbWUoIHJlYWQudGFibGUoZ3pmaWxlKHJlZl9kYXRhW1tpXV1bMV0pLCBoZWFkZXI9VFJVRSwgc2VwPSJcdCIsIHJvdy5uYW1lcz1OVUxMKSApCiAgICAKICAgICMjIyMjIEFkZCBwcmV4aXQgdG8gc2FtcGxlIG5hbWVzCiAgICBjb2xuYW1lcyhkYXRhc2V0LmNvdW50cykgPC0gcGFzdGUodW5pcXVlKHRhcmdldC5jb21iWywiRGF0YXNldCJdKVtpXSwgY29sbmFtZXMoZGF0YXNldC5jb3VudHMpLCBzZXAgPSAiLiIpCiAgICAKICAgICMjIyMjIExpc3QgZ2VuZXMgcHJlc2VudCBpbiBpbmRpdmlkYWwgZmlsZXMKICAgIGdlbmVfbGlzdCA8LSBjKCBnZW5lX2xpc3QsIGFzLnZlY3RvcihkYXRhc2V0LmNvdW50c1ssMV0pICkKICAgIAogICAgIyMjIyMgTWVyZ2UgdGhlIGV4cHJlc3Npb24gZGF0YXNldHMgYW5kIG1ha2Ugc3VyZSB0aGF0IHRoZSBnZW5lcyBvcmRlciBpcyB0aGUgc2FtZQogICAgZGF0YXNldHMuY29tYiA8LSBtZXJnZSggZGF0YXNldHMuY29tYiwgZGF0YXNldC5jb3VudHMsIGJ5PTEsIGFsbCA9IEZBTFNFLCBzb3J0PSBUUlVFKQogIH0KICAKICAjIyMjIyBVc2UgZ2VuZSBJRHMgYXMgcm93bmFtZXMKICByb3duYW1lcyhkYXRhc2V0cy5jb21iKSA8LSBkYXRhc2V0cy5jb21iWywxXQogIGRhdGFzZXRzLmNvbWIgPC0gZGF0YXNldHMuY29tYlssIC0xXQogIAogICMjIyMjIE1ha2Ugc3ludGFjdGljYWxseSB2YWxpZCBuYW1lcwogIGNvbG5hbWVzKGRhdGFzZXRzLmNvbWIpIDwtIG1ha2UubmFtZXMoY29sbmFtZXMoZGF0YXNldHMuY29tYikpCiAgCiAgIyMjIyMgTWFrZSBzdXJlIHRoYXQgdGhlIHRhcmdldCBmaWxlIGNvbnRhaW5zIGluZm8gb25seSBhYm91dCBzYW1wbGVzIHByZXNlbnQgaW4gdGhlIGRhdGEgbWF0cml4CiAgdGFyZ2V0LmNvbWIgPC0gdGFyZ2V0LmNvbWJbIHJvd25hbWVzKHRhcmdldC5jb21iKSAlaW4lIGNvbG5hbWVzKGRhdGFzZXRzLmNvbWIpLCAgXQogIAogICMjIyMjIE1ha2Ugc3VyZSB0aGF0IHRoZSBzYW1wbGVzIG9yZGVyIGluIHRoZSBkYXRhIG1hdHJpeCBpcyB0aGUgc2FtZSBhcyBpbiB0aGUgdGFyZ2V0IGZpbGUgCiAgZGF0YXNldHMuY29tYiA8LSBkYXRhc2V0cy5jb21iWyAsIHJvd25hbWVzKHRhcmdldC5jb21iKSBdCiAgCiAgIyMjIyMgSWRlbnRpZnkgZ2VuZXMgdGhhdCB3ZXJlIG5vdCBwcmVzZW50IGFjcm9zcyBhbGwgcGVyLXNhbXBlbCBmaWxlcyBhbmQgd2VyZSBvbW1pdGVkIGluIHRoZSBtZXJnZWQgbWF0cml4CiAgZ2VuZV9saXN0IDwtIHVuaXF1ZShnZW5lX2xpc3QpCiAgZ2VuZV9saXN0Lm1pc3NpbmcgPC0gZ2VuZV9saXN0WyBnZW5lX2xpc3QgJSFpbiUgcm93bmFtZXMoZGF0YXNldHMuY29tYikgXQogIAogICMjIyMjIFdyaXRlIGxpc3Qgb2YgbWlzc2luZyBnZW5lcyBpbnRvIGEgZmlsZQogIGlmICggbGVuZ3RoKGdlbmVfbGlzdC5taXNzaW5nKSA+IDAgKSB7CiAgICB3cml0ZS50YWJsZShwcmVwYXJlMndyaXRlKGdlbmVfbGlzdC5taXNzaW5nKSwgZmlsZSA9IHBhc3RlMChyZXBvcnRfZGlyLCAiLyIsIHNhbXBsZV9uYW1lLCAiLlJOQXNlcV9yZXBvcnQubWlzc2luZ19nZW5lcy50eHQiKSwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCByb3cubmFtZXM9VFJVRSwgYXBwZW5kID0gRkFMU0UgKQogIH0KICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UgYW5kIHJldHVybiBvdXRwdXQKICBybShzYW1wbGVfbmFtZSwgc2FtcGxlX2NvdW50cywgcmVmX2RhdGEsIHRhcmdldC5leHQsIHRhcmdldC5pbnQsIHRhcmdldC5zYW1wbGUsIGRhdGFzZXQuY291bnRzLCBnZW5lX2xpc3QsIGdlbmVfbGlzdC5taXNzaW5nKQogIHJldHVybiggbGlzdChkYXRhc2V0cy5jb21iLCB0YXJnZXQuY29tYikgKQp9CgojIyMjIyBBc3NpZ24gY29sb3VycyB0byBkaWZmZXJlbnQgZWxlbWVudHMKZ2V0Q29sb3VycyA8LSBmdW5jdGlvbihlbGVtZW50cykgewogIAogICMjIyMjIFByZWRlZmluZWQgc2VsZWN0aW9uIG9mIGNvbG91cnMgZm9yIGVsZW1lbnRzCiAgaWYgKCBsZW5ndGgodW5pcXVlKGVsZW1lbnRzKSkgPT0gMyApIHsKICAgIGVsZW1lbnRzLmNvbG91cnMgPC0gYygicG93ZGVyYmx1ZSIsICJyZWQiLCAiZ3JheTUwIikKICB9IGVsc2UgaWYgKCBsZW5ndGgodW5pcXVlKGVsZW1lbnRzKSkgPT0gNCApIHsKICAgIGVsZW1lbnRzLmNvbG91cnMgPC0gYygicG93ZGVyYmx1ZSIsICJmb3Jlc3RncmVlbiIsICJyZWQiLCAiZ3JheTUwIikKICB9IGVsc2UgewogICAgZWxlbWVudHMuY29sb3VycyA8LSByYWluYm93KGxlbmd0aChlbGVtZW50cykpCiAgfQogIAogIGYuZWxlbWVudHMgPC0gZmFjdG9yKGVsZW1lbnRzLCBsZXZlbHMgPSB1bmlxdWUoZWxlbWVudHMpKQogIHZlYy5lbGVtZW50cyA8LSBlbGVtZW50cy5jb2xvdXJzWzE6bGVuZ3RoKGxldmVscyhmLmVsZW1lbnRzKSldCiAgZWxlbWVudHMuY29sb3VyIDwtIHJlcCgwLGxlbmd0aChmLmVsZW1lbnRzKSkKICBmb3IgKGkgaW4gMTpsZW5ndGgoZi5lbGVtZW50cykpCiAgICBlbGVtZW50cy5jb2xvdXJbaV0gPC0gdmVjLmVsZW1lbnRzWyBmLmVsZW1lbnRzW2ldPT1sZXZlbHMoZi5lbGVtZW50cyldCiAgCiAgcmV0dXJuKCBsaXN0KHZlYy5lbGVtZW50cywgZWxlbWVudHMuY29sb3VyKSApCn0KCiMjIyMjIENhbGN1bGF0ZSBUUE0gZnJvbSBSUEtNIChmcm9tIGh0dHA6Ly9sdWlzdmFsZXNpbHZhLmNvbS9kYXRhc2ltcGxlL3JuYS1zZXFfdW5pdHMuaHRtbCApCnRwbV9mcm9tX3Jwa20gPC0gZnVuY3Rpb24oeCkgewogIHJwa20uc3VtIDwtIGNvbFN1bXMoeCkKICByZXR1cm4odCh0KHgpIC8gKDFlLTA2ICogcnBrbS5zdW0pKSkKfQoKIyMjIyMgRnVuY3Rpb24gdG8gZ2VuZXJhdGUgYSBmdWxsLXJlc29sdXRpb24gcGRmIGltYWdlIGJlZm9yZSBnZW5lcmF0aW5nIGEgc21hbGwgaW1hZ2UgaW4gdGhlIGNodW5rIChmcm9tIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzM3ODM0MDUzL3doYXQtaXMtYS1zaW1wbGUtd2F5LXRvLXRodW1ibmFpbC1zb21lLXBsb3RzLWluLXItbWFya2Rvd24ta25pdHIgKQphbGxvd190aHVtYm5haWxzIDwtIGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICBpZiAoIWlzLm51bGwob3B0aW9ucyR0aHVtYikpIHsKICAgIGZpbGVuYW1lIDwtIHNwcmludGYoIiVzLmZ1bGwucGRmIiwgc3Ryc3BsaXQoYmFzZW5hbWUoeCksICJcXC4iKVtbMV1dWzFdKQogICAgYWJzb2x1dGVfcGF0aCA8LSBmaWxlLnBhdGgoZGlybmFtZSh4KSwgZmlsZW5hbWUpCgogICAgIyMjIyMgR2VuZXJhdGUgdGhlIGZ1bGwgcmVzb2x1dGlvbiBwZGYKICAgIHBkZihhYnNvbHV0ZV9wYXRoLCB3aWR0aCA9IG9wdGlvbnMkdGh1bWIkd2lkdGgsIGhlaWdodCA9IG9wdGlvbnMkdGh1bWIkaGVpZ2h0KQogICAgICBldmFsKHBhcnNlKHRleHQgPSBvcHRpb25zJGNvZGUpKQogICAgZGV2Lm9mZigpCgogICAgIyMjIyMgQWRkIGFuIGh0bWwgbGluayB0byB0aGUgbG93IHJlc29sdXRpb24gcG5nCiAgICBvcHRpb25zJGZpZy5saW5rID0gYWJzb2x1dGVfcGF0aAogIH0KCiAga25pdHI6Ojpob29rX3Bsb3RfbWRfYmFzZSh4LCBvcHRpb25zKQp9CgojIyMjIyBQZXJmb3JtIFBDQS4gVGhpcyBmdW5jdGlvbiBvdXRwdXRzIGEgbGlzdCB3aXRoIGRhdGFmcmFtZSBhbmQgc2FtcGxlcyBjb2xvdXJpbmcgaW5mbyByZWFkeSBmb3IgcGxvdHRpbmcKcGNhIDwtIGZ1bmN0aW9uKGRhdGEsIHRhcmdldHMsIHRpdGxlID0gIiIsIHJlcG9ydF9kaXIsIHN1ZmZpeCA9ICIiICkgewoKICAjIyMjIyBLZWVwIG9ubHkgZ2VuZXMgd2l0aCB2YXJpYW5jZSA+IDAgYWNyb3NzIGFsbCBzYW1wbGVzCiAgcnNkIDwtIGFwcGx5KGRhdGEsMSxzZCkKICBkYXRhLnN1YnNldCA8LSBkYXRhW3JzZD4wLF0KICAKICAjIyMjIyBQZXJmb3JtIFBDQQogIGRhdGEuc3Vic2V0X3BjYSA8LSBwcmNvbXAodChkYXRhLnN1YnNldCksIHNjYWxlPUZBTFNFKQogIAogICMjIyMjIEdldCB2YXJpYW5jZSBpbXBvcnRhbmNlIGZvciBhbGwgcHJpbmNpcGFsIGNvbXBvbmVudHMKICBpbXBvcnRhbmNlX3BjYSA8LSBzdW1tYXJ5KGRhdGEuc3Vic2V0X3BjYSkkaW1wb3J0YW5jZVsyLF0KICBpbXBvcnRhbmNlX3BjYSA8LSBwYXN0ZShyb3VuZCgxMDAqaW1wb3J0YW5jZV9wY2EsIDIpLCAiJSIsIHNlcD0iIikKICBuYW1lcyhpbXBvcnRhbmNlX3BjYSkgPC0gbmFtZXMoc3VtbWFyeShkYXRhLnN1YnNldF9wY2EpJGltcG9ydGFuY2VbMixdKQogICAgCiAgIyMjIyMgUHJlcGFyZSBkYXRhIGZyYW1lCiAgZGF0YS5zdWJzZXRfcGNhLmRmIDwtIGRhdGEuZnJhbWUodGFyZ2V0cyRUYXJnZXQsIHRhcmdldHMkRGF0YXNldCwgZGF0YS5zdWJzZXRfcGNhJHhbLCJQQzEiXSwgZGF0YS5zdWJzZXRfcGNhJHhbLCJQQzIiXSwgZGF0YS5zdWJzZXRfcGNhJHhbLCJQQzMiXSkKICBjb2xuYW1lcyhkYXRhLnN1YnNldF9wY2EuZGYpIDwtIGMoIlRhcmdldCIsICJEYXRhc2V0IiwgIlBDMSIsICJQQzIiLCAiUEMzIikKICAKICAjIyMjIyBBc3NpZ25lIGNvbG91cnMgdG8gdGFyZ2V0cyBhbmQgZGF0YXNldHMKICB0YXJnZXRzLmNvbG91ciA8LSBnZXRDb2xvdXJzKHRhcmdldHMkVGFyZ2V0KQogIGRhdGFzZXRzLmNvbG91ciA8LSBnZXRDb2xvdXJzKHRhcmdldHMkRGF0YXNldCkKICAKICAjIyMjIyBDcmVhdGUgYSBsaXN0IHdpdGggZGF0YWZyYW1lIGFuZCBzYW1wbGVzIGNvbG91cmluZyBpbmZvCiAgcGNhLmxpc3QgPC0gbGlzdChkYXRhLnN1YnNldF9wY2EuZGYsIGltcG9ydGFuY2VfcGNhLCB0YXJnZXRzLmNvbG91ciwgZGF0YXNldHMuY29sb3VyKQogIG5hbWVzKHBjYS5saXN0KSA8LSBjKCJwY2EuZGYiLCAiaW1wb3J0YW5jZV9wY2EiLCAidGFyZ2V0cyIsICJkYXRhc2V0cyIpCiAgCiAgIyMjIyMgQ2hhbmdlIHRoZSBkYXRhc2V0cyBsZXZlbHMgb3JkZXIKICBkYXRhLnN1YnNldF9wY2EuZGYkVGFyZ2V0IDwtIGZhY3RvcihkYXRhLnN1YnNldF9wY2EuZGYkVGFyZ2V0LCBsZXZlbHMgPSB1bmlxdWUoZGF0YS5zdWJzZXRfcGNhLmRmJFRhcmdldCkpCiAgCiAgIyMjIyMgR2VuZXJhdGUgUENBIDItRCBwbG90CiAgcGNhX3Bsb3QgPC0gcGxvdF9seShkYXRhLnN1YnNldF9wY2EuZGYsIHggPSB+UEMxLCB5ID0gflBDMiwgY29sb3IgPSB+VGFyZ2V0LCB0ZXh0PXBhc3RlKHRhcmdldHMkVGFyZ2V0LCByb3duYW1lcyhkYXRhLnN1YnNldF9wY2EuZGYpLCBzZXA9IjogIiksIGNvbG9ycyA9IHRhcmdldHMuY29sb3VyW1sxXV0sIHR5cGU9J3NjYXR0ZXInLCBtb2RlID0gIm1hcmtlcnMiLCBtYXJrZXIgPSBsaXN0KHNpemU9MTAsIG9wYWNpdHkgPSAwLjcpLCB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gNTAwKSAlPiUKICBsYXlvdXQodGl0bGUgPSB0aXRsZSwgeGF4aXMgPSBsaXN0KHRpdGxlID0gcGFzdGUoICJQQzEiLCAiICgiLGltcG9ydGFuY2VfcGNhWyJQQzEiXSwiKSIsc2VwPSIiKSksIHlheGlzID0gbGlzdCh0aXRsZSA9IHBhc3RlKCAiUEMyIiwgIiAoIixpbXBvcnRhbmNlX3BjYVsiUEMyIl0sIikiLHNlcD0iIikpLCBtYXJnaW4gPSBsaXN0KGw9NTAsIHI9NTAsIGI9NTAsIHQ9MzAsIHBhZD00KSwgYXV0b3NpemUgPSBGQUxTRSwgc2hvd2xlZ2VuZCA9IFRSVUUsIGxlZ2VuZCA9IGxpc3Qob3JpZW50YXRpb24gPSAidiIsIHkgPSAwLjkpKQoKICAjIyMjIyBHZW5lcmF0ZSBTY3JlZS1wbG90CiAgZGF0YS5zdWJzZXRfc2NyZWUuZGYgPC0gZGF0YS5mcmFtZShwYXN0ZTAoIlBDICIsIGMoMTpsZW5ndGgoaW1wb3J0YW5jZV9wY2EpKSksIGFzLm51bWVyaWMoZ3N1YigiJSIsICIiLGltcG9ydGFuY2VfcGNhKSkpCmNvbG5hbWVzKGRhdGEuc3Vic2V0X3NjcmVlLmRmKSA8LSBjKCJQQyIsICJWYXJpYW5jZXMiKQoKICAjIyMjIyBUaGUgZGVmYXVsdCBvcmRlciB3aWxsIGJlIGFscGhhYmV0aXplZCB1bmxlc3Mgc3BlY2lmaWVkIGFzIGJlbG93CiAgZGF0YS5zdWJzZXRfc2NyZWUuZGYkUEMgPC0gZmFjdG9yKGRhdGEuc3Vic2V0X3NjcmVlLmRmJFBDLCBsZXZlbHMgPSBkYXRhLnN1YnNldF9zY3JlZS5kZltbIlBDIl1dKQogIAogIHNjcmVlX3Bsb3QgPC0gcGxvdF9seShkYXRhLnN1YnNldF9zY3JlZS5kZiwgeCA9IH5QQywgeSA9IH5WYXJpYW5jZXMsIHR5cGUgPSAnYmFyJywgd2lkdGggPSA4MDAsIGhlaWdodCA9IDM1MCkgJT4lCiAgICBsYXlvdXQodGl0bGUgPSB0aXRsZSwgeGF4aXMgPSBsaXN0KHRpdGxlID0gIiIpLCBtYXJnaW4gPSBsaXN0KGw9NTAsIHI9NTAsIGI9MTAwLCB0PTMwLCBwYWQ9NCksIGF1dG9zaXplID0gRikKICAKICAjIyMjIyBDcmVhdGUgZGlyZWN0b3J5IGZvciB0aGUgcGxvdHMKICBQQ0FwbG90RGlyIDwtIHBhc3RlKHJlcG9ydF9kaXIsICJJbnB1dERhdGFQbG90cyIsIHNlcCA9ICIvIikKICBpZiAoICFmaWxlLmV4aXN0cyhQQ0FwbG90RGlyKSApIHsKICAgIGRpci5jcmVhdGUoUENBcGxvdERpciwgcmVjdXJzaXZlPVRSVUUpCiAgfQogIAogICMjIyMjIFNhdmUgaW50ZXJhY3RpdmUgcGxvdCBhcyBodG1sIGZpbGUKICBzYXZlV2lkZ2V0Rml4KHBjYV9wbG90LCBmaWxlID0gcGFzdGUwKFBDQXBsb3REaXIsICIvcGNhX3Bsb3QiLCBzdWZmaXgsICIuaHRtbCIpKQogIHNhdmVXaWRnZXRGaXgoc2NyZWVfcGxvdCwgZmlsZSA9IHBhc3RlMChQQ0FwbG90RGlyLCAiL3NjcmVlX3Bsb3QiLCBzdWZmaXgsICIuaHRtbCIpKQogIAogIHJldHVybiggbGlzdChwY2EubGlzdCwgcGNhX3Bsb3QsIHNjcmVlX3Bsb3QpICkKICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UKICBybShkYXRhLCB0YXJnZXRzLCByc2QsIGRhdGEuc3Vic2V0LCBkYXRhLnN1YnNldF9wY2EsIGltcG9ydGFuY2VfcGNhLCBkYXRhLnN1YnNldF9wY2EuZGYsIHRhcmdldHMuY29sb3VyLCBkYXRhc2V0cy5jb2xvdXIsIHBjYS5saXN0LCBkYXRhLnN1YnNldF9zY3JlZS5kZiwgUGxvdHNEaXIpCiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCn0KCiMjIyMjIENvbnZlcnQgYSB2ZWN0b3Igb2YgbnVtYmVycyBpbnRvIGNvcnJlc3BvbmRpbmcgdmVjdG9yIG9mIHRoZWlyIHBlcmNlbnRpbGVzCnBlcmMucmFuayA8LSBmdW5jdGlvbih4KSB0cnVuYyhyYW5rKHgpKSoxMDAvbGVuZ3RoKHgpCgojIyMjIyBQZXJmb3JtIHJhbmdlIHN0YW5kYXJkaXphdGlvbiBiZXR3ZWVuIDAgYW5kIDEgKGZvciB0aGUgY3VtdWxhdGl2ZSBzdW1zKQpzdGFuZGFyZGl6YXRpb24gPC0gZnVuY3Rpb24oeCkgYyh4LW1pbih4KSkvKG1heCh4KS1taW4oeCkpCgojIyMjIyBDYWxjdWxhdGluZyBjdW11bGF0aXZlIHN1bSBmb3Igd2hpbGUga2VlcGluZyB0aGUgb3JpZ2luYWwgZGF0YSBvcmRlcgpjdW1zdW1fb3JkZXJlZCA8LSBmdW5jdGlvbih4KSB7CiAgCiAgIyMjIyMgUGVyZm9ybSByYW5nZSBzdGFuZGFyZGl6YXRpb24gYmV0d2VlbiAwIGFuZCAxLCBvdGhlcndpc2UgdGhlIG5lZ2F0aXZlIHZhbHVlcyBhcmUgc3VtbWVkIHVwCiAgc3RhbmRhcmlzZWQgPC0gc3RhbmRhcmRpemF0aW9uKHgpCiAgCiAgIyMjIyMgU29ydCBhbmQgY3Vtc3VtIHZhbHVlcwogIHNvcnRlZF9jdW1zdW0gPC0gY3Vtc3VtKHNvcnQoc3RhbmRhcmlzZWQpKQogIAogICMjIyMjIFJlc3RvcmUgdGhlIG9yaWdpbmFsIGVsZW1lbnRzIG9yZGVyCiAgb3JkZXJlZF9jdW1zdW0gPC0gc29ydGVkX2N1bXN1bVsgbmFtZXMoc3RhbmRhcmlzZWQpIF0KICAKICAjIyMjIyBQZXJmb3JtIHJhbmdlIHN0YW5kYXJkaXphdGlvbiBiZXR3ZWVuIDAgYW5kIDEsIG90aGVyd2lzZSB0aGUgbmVnYXRpdmUgdmFsdWVzIGFyZSBzdW1tZWQgdXAKICBzdGFuZGFyaXNlZF9jdW1zdW0gPC0gc3RhbmRhcmRpemF0aW9uKG9yZGVyZWRfY3Vtc3VtKQogIAogICMjIyMjIENsZWFuIHRoZSBzcGFjZSBhbmQgcmV0dXJuIG91dHB1dAogIHJtKHgsIHN0YW5kYXJpc2VkLCBzb3J0ZWRfY3Vtc3VtLCBvcmRlcmVkX2N1bXN1bSkKICByZXR1cm4oIHN0YW5kYXJpc2VkX2N1bXN1bSApCn0KCiMjIyMjIENoZWNrIGZvciBuZWFyZXN0IHZhbHVlIGluIGEgdmVjdG9yCm5lYXJlc3RfcG9zaXRpb24gPC0gZnVuY3Rpb24odmVjdG9yLCB4KSB7CiAgCiAgeSA8LSB3aGljaC5taW4oYWJzKHZlY3RvciAtIHgpKQogIAogICMjIyMjIENsZWFuIHRoZSBzcGFjZSBhbmQgcmV0dXJuIG91dHB1dAogIHJtKHZlY3RvciwgeCkKICByZXR1cm4oIHkgKQp9CgojIyMjIyBDYWxjdWxhdGUgZ2VuZS13aXNlIG1lZGlhbiwgc2QsIHF1YW50aWxlcyBhbmQgY3VtdWxhdGl2ZSBmcmFuY3Rpb25zIGZvciBleHByZXNzaW9uIGRhdGEKZXhwckdyb3Vwc1N0YXRzX2dlbmVXaXNlIDwtIGZ1bmN0aW9uKGRhdGEsIHRhcmdldHMpIHsKICAKICAjIyMjIyBQZXJmb3JtIFotc2NvcmUgdHJhbnNmb3JtYXRpb24gb2YgdGhlIGV4cHJlc3Npb24gdmFsdWVzCiAgZGF0YS56IDwtIHQoYXBwbHkoZGF0YSwgMSwgc2NhbGUsIHNjYWxlID0gVFJVRSkpCiAgY29sbmFtZXMoZGF0YS56KSA8LSBjb2xuYW1lcyhkYXRhKQogIAogICMjIyMjIFJlbW92ZSByb3dzIHdpdGggcG90ZW50aWFsIE5BJ3MsIHdoaWNoIGlzIGR1ZSB0byBTRCA9IDAgYWNyb3NzIGFsbCBzYW1wbGVzCiAgZGF0YS56IDwtIGRhdGEueltyb3dTdW1zKCFpcy5uYShkYXRhLnopKSA+IDAsICwgZHJvcCA9IEZBTFNFXQogIGRhdGEgPC0gZGF0YVsgcm93bmFtZXMoZGF0YSkgJWluJSByb3duYW1lcyhkYXRhLnopLCAsIGRyb3AgPSBGQUxTRV0KICAKICAjIyMjIyBQZXJmb3JtIHRoZSBnZW5lLXdpc2UgY2FsY3VsYXRpb25zIGFjcm9zcyBhbGwgZ3JvdXBzCiAgIyMjIyMgQ29udmVydCBhIGV4cHJlc3Npb24gdmFsdWVzIGludG8gY29ycmVzcG9uZGluZyBwZXJjZW50aWxlcwogIGRhdGEucSA8LSB0KGFwcGx5KGRhdGEsIDEsIHBlcmMucmFuaykpCiAKICAjIyMjIyBDYWxjdWxhdGUgY3VtdWxhdGl2ZSBzdW1zIGFuZCBwZXJmb3JtIHJhbmdlIHN0YW5kYXJkaXphdGlvbiBiZXR3ZWVuIDAgYW5kIDEKICBkYXRhLmN1bSA8LSB0KGFwcGx5KGRhdGEsIDEsIGN1bXN1bV9vcmRlcmVkKSkKIAogICMjIyMjIENyZWF0ZSBsaXN0cyB3aXRoIHN0YXRzIGZvciBlYWNoIGdyb3VwIGFuZCBnZW5lCiAgdGFyZ2V0cy5saXN0IDwtIHVuaXF1ZSh0YXJnZXRzJFRhcmdldCkKICBncm91cF9zdGF0cy5saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCh0YXJnZXRzLmxpc3QpKQogIG5hbWVzKGdyb3VwX3N0YXRzLmxpc3QpIDwtIHRhcmdldHMubGlzdAogIAogICMjIyMgRm9yIGVhY2ggZ3JvdXAuLi4KICBmb3IgKCBncm91cCBpbiB0YXJnZXRzLmxpc3QgKSB7CiAgICAKICAgICMjIyMjIEZvciBncm91cHMgd2l0aCA+IDEgc2FtcGxlIGdldCB0aGUgbWVkaWFuIHZhbHVlcyBmb3IgZWFjaCBnZW5lCiAgICBpZiAoIHN1bShjKHRhcmdldHMkVGFyZ2V0ICVpbiUgZ3JvdXApLCBuYS5ybSA9IFRSVUUpID4gMSAmJiBucm93KGRhdGEpID4gMSApICB7CiAgICAgIAogICAgICAjIyMjIyBFeHRyYWN0IHRoZSBtZWRpYW4gZXhwcmVzc2lvbiB2YWx1ZXMKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCByb3dNZWRpYW5zKGRhdGFbICwgY29sbmFtZXMoZGF0YSlbIHRhcmdldHMkVGFyZ2V0ICVpbiUgZ3JvdXAgXSBdKSkKCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIGV4cHJlc3Npb24gc2QgdmFsdWVzCiAgICAgIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gPC0gY2JpbmQoZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSwgcm93U2RzKGRhdGFbICwgY29sbmFtZXMoZGF0YSlbIHRhcmdldHMkVGFyZ2V0ICVpbiUgZ3JvdXAgXSBdKSkKICAgICAgCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIG1lZGlhbiBaLXNjb3JlcwogICAgICBncm91cF9zdGF0cy5saXN0W1tncm91cF1dIDwtIGNiaW5kKGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0sIHJvd01lZGlhbnMoZGF0YS56WyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0gXSkpCgogICAgICAjIyMjIyBFeHRyYWN0IHRoZSBtZWRpYW4gcGVyY2VudGlsZXMKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCByb3dNZWRpYW5zKGRhdGEucVsgLCBjb2xuYW1lcyhkYXRhKVsgdGFyZ2V0cyRUYXJnZXQgJWluJSBncm91cCBdIF0pKQogICAgICAKICAgICAgIyMjIyMgRXh0cmFjdCB0aGUgY3VtdWxhdGl2ZSBmcmFjdGlvbiBjb3JyZXNwb25kaW5nIHRvIHRoZSBtZWRpYW4gWi1zY29yZQogICAgICAjIyMjIyBGaXJzdCwgbmVlZCB0byBnZXQgdGhlIHBvc2l0aW9uIG9mIHRoZSBaLXNjb3JlIG5lYXJlc3QgdG8gdGhlIG1lZGlhbiBaLXNjb3JlLCBhbmQgdGhlbiBleHRyYWN0IHRoZSBjdW11bGF0aXZlIHZhbHVlIGF0IHRoaXMgcG9zaXRpb24KICAgICAgZGF0YS56Lm1lZGlhbl9wb3MgPC0gYXBwbHkoZGF0YS56LCAxLCBuZWFyZXN0X3Bvc2l0aW9uLCBtZWRpYW4oZGF0YS56WyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwLCBkcm9wID0gRkFMU0UgXSBdKSkKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCBkYXRhLmN1bVsgZGF0YS56Lm1lZGlhbl9wb3MgXSApCiAgICAgIAogICAgICBncm91cF9zdGF0cy5saXN0W1tncm91cF1dIDwtIGFzLmRhdGEuZnJhbWUoZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSkKICAgICAgbmFtZXMoIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gKSA8LSBjKCJtZWRpYW4iLCAic2QiLCAieiIsICJxdWFudGlsZSIsICJjdW0iKQogICAgICByb3duYW1lcyggZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSApIDwtIHJvd25hbWVzKGRhdGEpCiAgICAgIAogICAgfSBlbHNlIGlmICggc3VtKGModGFyZ2V0cyRUYXJnZXQgJWluJSBncm91cCksIG5hLnJtID0gVFJVRSkgPiAxICYmIG5yb3coZGF0YSkgPT0gMSApIHsKICAgICAgCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIG1lZGlhbiBleHByZXNzaW9uIHZhbHVlcwogICAgICBncm91cF9zdGF0cy5saXN0W1tncm91cF1dIDwtIGNiaW5kKGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0sIG1lZGlhbihkYXRhWyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwLCBkcm9wID0gRkFMU0UgXSBdKSkKCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIGV4cHJlc3Npb24gc2QgdmFsdWVzCiAgICAgIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gPC0gY2JpbmQoZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSwgc2QoZGF0YVsgLCBjb2xuYW1lcyhkYXRhKVsgdGFyZ2V0cyRUYXJnZXQgJWluJSBncm91cCwgZHJvcCA9IEZBTFNFIF0gXSkpCiAgICAgIAogICAgICAjIyMjIyBFeHRyYWN0IHRoZSBtZWRpYW4gWi1zY29yZXMKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCBtZWRpYW4oZGF0YS56WyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwLCBkcm9wID0gRkFMU0UgXSBdKSkKCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIG1lZGlhbiBwZXJjZW50aWxlcwogICAgICBncm91cF9zdGF0cy5saXN0W1tncm91cF1dIDwtIGNiaW5kKGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0sIG1lZGlhbihkYXRhLnFbICwgY29sbmFtZXMoZGF0YSlbIHRhcmdldHMkVGFyZ2V0ICVpbiUgZ3JvdXAsIGRyb3AgPSBGQUxTRSBdIF0pKQogICAgICAKICAgICAgIyMjIyMgRXh0cmFjdCB0aGUgY3VtdWxhdGl2ZSBmcmFjdGlvbiBjb3JyZXNwb25kaW5nIHRvIHRoZSBtZWRpYW4gWi1zY29yZQogICAgICAjIyMjIyBGaXJzdCwgbmVlZCB0byBnZXQgdGhlIHBvc2l0aW9uIG9mIHRoZSBaLXNjb3JlIG5lYXJlc3QgdG8gdGhlIG1lZGlhbiBaLXNjb3JlLCBhbmQgdGhlbiBleHRyYWN0IHRoZSBjdW11bGF0aXZlIHZhbHVlIGF0IHRoaXMgcG9zaXRpb24KICAgICAgZGF0YS56Lm1lZGlhbl9wb3MgPC0gbmVhcmVzdF9wb3NpdGlvbiggZGF0YS56LCBtZWRpYW4oZGF0YS56WyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwLCBkcm9wID0gRkFMU0UgXSBdKSkKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCBkYXRhLmN1bVsgZGF0YS56Lm1lZGlhbl9wb3MgXSApIAoKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBhcy5kYXRhLmZyYW1lKGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0pCiAgICAgIG5hbWVzKCBncm91cF9zdGF0cy5saXN0W1tncm91cF1dICkgPC0gYygibWVkaWFuIiwgInNkIiwgInoiLCAicXVhbnRpbGUiLCAiY3VtIikKICAgICAgcm93bmFtZXMoIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gKSA8LSByb3duYW1lcyhkYXRhKQogICAgICAKICAgIH0gZWxzZSB7CgogICAgICAjIyMjIyBFeHRyYWN0IHRoZSBtZWRpYW4gZXhwcmVzc2lvbiB2YWx1ZXMKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCBkYXRhWyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0gXSkKCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIGV4cHJlc3Npb24gc2QgdmFsdWVzCiAgICAgIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gPC0gY2JpbmQoZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSwgcmVwKCBOQSwgbnJvdyhkYXRhKSkpCiAgICAgIAogICAgICAjIyMjIyBFeHRyYWN0IHRoZSBtZWRpYW4gWi1zY29yZXMKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBjYmluZChncm91cF9zdGF0cy5saXN0W1tncm91cF1dLCBkYXRhLnpbICwgY29sbmFtZXMoZGF0YSlbIHRhcmdldHMkVGFyZ2V0ICVpbiUgZ3JvdXAgXSBdKQoKICAgICAgIyMjIyMgRXh0cmFjdCB0aGUgbWVkaWFuIHBlcmNlbnRpbGVzCiAgICAgIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gPC0gY2JpbmQoZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSwgZGF0YS5xWyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0gXSkKICAgICAgCiAgICAgICMjIyMjIEV4dHJhY3QgdGhlIG1lZGlhbiBjdW11bGF0aXZlIGZyYWN0aW9uCiAgICAgIGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0gPC0gY2JpbmQoZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSwgZGF0YS5jdW1bICwgY29sbmFtZXMoZGF0YSlbIHRhcmdldHMkVGFyZ2V0ICVpbiUgZ3JvdXAgXSBdKQogICAgICAKICAgICAgZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSA8LSBhcy5kYXRhLmZyYW1lKGdyb3VwX3N0YXRzLmxpc3RbW2dyb3VwXV0pCiAgICAgIG5hbWVzKCBncm91cF9zdGF0cy5saXN0W1tncm91cF1dICkgPC0gYygibWVkaWFuIiwgInNkIiwgInoiLCAicXVhbnRpbGUiLCJjdW0iKQogICAgICByb3duYW1lcyggZ3JvdXBfc3RhdHMubGlzdFtbZ3JvdXBdXSApIDwtIHJvd25hbWVzKGRhdGEpCiAgICAgfQogIH0KICAKICAjIyMjIyBGaW5hbGx5LCBleHRyYWN0IGN1bXVsYXRpdmUgdmFsdWVzIGZvciBlYWNoIGdlbmUgd2l0aGluIGluZGl2aWR1YWwgZ3JvdXBzCiAgZ2VuZV9zdGF0cy5saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCh0YXJnZXRzLmxpc3QpKQogIG5hbWVzKGdlbmVfc3RhdHMubGlzdCkgPC0gdGFyZ2V0cy5saXN0CiAgCiAgIyMjIyBGb3IgZWFjaCBncm91cC4uLgogIGZvciAoIGdyb3VwIGluIHRhcmdldHMubGlzdCApIHsKICAgIAogICAgIyMjIyMgRXh0cmFjdCBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcwogICAgZ2VuZV9zdGF0cy5saXN0W1tncm91cF1dJG1lZGlhbiA8LSBkYXRhWyAsIGNvbG5hbWVzKGRhdGEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0sIGRyb3AgPSBGQUxTRSBdCiAgICAKICAgICMjIyMjIEV4dHJhY3QgcGVyLWdlbmUgei1zY29yZSB2YWx1ZXMKICAgIGdlbmVfc3RhdHMubGlzdFtbZ3JvdXBdXSR6IDwtIGRhdGEuelsgLCBjb2xuYW1lcyhkYXRhLnopWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0sIGRyb3AgPSBGQUxTRSBdCiAgICAKICAgICMjIyMjIEV4dHJhY3QgcGVyLWdlbmUgcGVyY2VudGlsZSB2YWx1ZXMKICAgIGdlbmVfc3RhdHMubGlzdFtbZ3JvdXBdXSRxIDwtIGRhdGEucVsgLCBjb2xuYW1lcyhkYXRhLnEpWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0sIGRyb3AgPSBGQUxTRSBdCiAgICAKICAgICMjIyMjIEV4dHJhY3QgcGVyLWdlbmUgY3VtdWxhdGl2ZSB2YWx1ZXMKICAgIGdlbmVfc3RhdHMubGlzdFtbZ3JvdXBdXSRjdW0gPC0gZGF0YS5jdW1bICwgY29sbmFtZXMoZGF0YS5jdW0pWyB0YXJnZXRzJFRhcmdldCAlaW4lIGdyb3VwIF0sIGRyb3AgPSBGQUxTRSBdCiAgfQogIAogICMjIyMjIENsZWFuIHRoZSBzcGFjZSBhbmQgcmV0dXJuIG91dHB1dAogIHJtKGRhdGEsIHRhcmdldHMsIGRhdGEueiwgZGF0YS5xLCBkYXRhLmN1bSwgdGFyZ2V0cy5saXN0LCBkYXRhLnoubWVkaWFuX3BvcykKICByZXR1cm4oIGxpc3QoIGdyb3VwX3N0YXRzLmxpc3QsIGdlbmVfc3RhdHMubGlzdCkgKQp9CgojIyMjIyBDYWxjdWxhdGUgZ3JvdXAtd2lzZSBtZWRpYW4sIHNkLCBxdWFudGlsZXMgYW5kIGN1bXVsYXRpdmUgZnJhbmN0aW9ucyBmb3IgZXhwcmVzc2lvbiBkYXRhIGZyb20gc3BlY2lmaWMgc2FtcGxlIGdyb3VwCmV4cHJHcm91cFN0YXRzX2dyb3VwV2lzZSA8LSBmdW5jdGlvbihkYXRhLCB0YXJnZXRzLCB0YXJnZXQpIHsKICAKICAjIyMjIyBTdWJzZXQgZGF0YSBmb3IgZGVmaW5lZCBiaW9sb2dpY2FsIGdyb3VwCiAgZGF0YS5ncm91cCA8LSBkYXRhWywgdGFyZ2V0cyRUYXJnZXQgJWluJSB0YXJnZXQgXQogIAogICMjIyMjIEZvciBncm91cHMgd2l0aCA+IDEgc2FtcGxlIGdldCB0aGUgbWVkaWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gZm9yIGVhY2ggZ2VuZQogIGlmICggIWlzLm51bGwobmNvbChkYXRhLmdyb3VwKSkgKSAgewogICAgCiAgICBkYXRhLmdyb3VwLm1lZGlhbiA8LSByb3dNZWRpYW5zKGRhdGEuZ3JvdXApCiAgICBuYW1lcyhkYXRhLmdyb3VwLm1lZGlhbikgPC0gcm93bmFtZXMoZGF0YS5ncm91cCkKICAgIGRhdGEuZ3JvdXAubWVkaWFuIDwtIHNvcnQoZGF0YS5ncm91cC5tZWRpYW4pCiAgICBkYXRhLmdyb3VwLnNkIDwtIHJvd1NkcyhkYXRhLmdyb3VwKQogICAgCiAgfSBlbHNlIHsKICAgIGRhdGEuZ3JvdXAubWVkaWFuIDwtIHNvcnQoZGF0YS5ncm91cCkKICAgIGRhdGEuZ3JvdXAuc2QgPC0gcmVwKCBOQSwgbGVuZ3RoKGRhdGEuZ3JvdXApKQogIH0KICAKICAjIyMjIyBNYWtlIHN1cmUgdGhlIG1lZGlhbiBhbmQgc2QgdmVjdG9ycyBoYXZlIHRoZSBzYW1lIGdlbmUgb3JkZXIKICBuYW1lcyhkYXRhLmdyb3VwLnNkKSA8LSByb3duYW1lcyhkYXRhLmdyb3VwKQogIGRhdGEuZ3JvdXAuc2QgPC0gZGF0YS5ncm91cC5zZFtuYW1lcyhkYXRhLmdyb3VwLm1lZGlhbildCgogICMjIyMjIENvbnZlcnQgYSBleHByZXNzaW9uIHZhbHVlcyBpbnRvIGNvcnJlc3BvbmRpbmcgcGVyY2VudGlsZXMKICBkYXRhLmdyb3VwLnEgPC0gcGVyYy5yYW5rKGRhdGEuZ3JvdXAubWVkaWFuKQogIAogICMjIyMjIFBlcmZvcm0gcmFuZ2Ugc3RhbmRhcmRpemF0aW9uIGJldHdlZW4gMCBhbmQgMSAoZm9yIHRoZSBjdW11bGF0aXZlIHN1bXMpLCBvdGhlcndpc2UgdGhlIG5lZ2F0aXZlIHZhbHVlcyBhcmUgc3VtbWVkIHVwCiAgZGF0YS5ncm91cC5zIDwtIHNvcnQoc3RhbmRhcmRpemF0aW9uKGRhdGEuZ3JvdXAubWVkaWFuKSkKICAKICAjIyMjIyBDYWxjdWxhdGUgY3VtdWxhdGl2ZSBzdW1zIGFuZCBwZXJmb3JtIHJhbmdlIHN0YW5kYXJkaXphdGlvbiBiZXR3ZWVuIDAgYW5kIDEgCiAgZGF0YS5ncm91cC5jdW0gPC0gc3RhbmRhcmRpemF0aW9uKGN1bXN1bShkYXRhLmdyb3VwLnMpKQogIAogICMjIyMjIFBlcmZvcm0gWi1zY29yZSB0cmFuc2Zvcm1hdGlvbiBvZiB0aGUgbWVkaWFuIGV4cHJlc3Npb24gdmFsdWVzCiAgZGF0YS5ncm91cC56IDwtIHNjYWxlKGRhdGEuZ3JvdXAubWVkaWFuLCBzY2FsZSA9IEZBTFNFKQogIAogICMjIyMjIE9yZ2FuaXNlIHRoZSBkYXRhIGludG8gZGF0YSBmcmFtZQogIGRhdGEuZ3JvdXAuZGYgPC0gYXMuZGF0YS5mcmFtZShjYmluZCggZGF0YS5ncm91cC5tZWRpYW4sIGRhdGEuZ3JvdXAuc2QsIGRhdGEuZ3JvdXAueiwgZGF0YS5ncm91cC5xLCBkYXRhLmdyb3VwLmN1bSkpCiAgbmFtZXMoZGF0YS5ncm91cC5kZikgPC0gYygibWVkaWFuIiwgInNkIiwgInoiLCAicXVhbnRpbGUiLCAiY3VtIikKICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UgYW5kIHJldHVybiBvdXRwdXQKICBybShkYXRhLCB0YXJnZXRzLCB0YXJnZXQsIGRhdGEuZ3JvdXAsIGRhdGEuZ3JvdXAubWVkaWFuLCBkYXRhLmdyb3VwLnNkLCBkYXRhLmdyb3VwLnEsIGRhdGEuZ3JvdXAucywgZGF0YS5ncm91cC5jdW0sIGRhdGEuZ3JvdXAueikKICByZXR1cm4oIGRhdGEuZ3JvdXAuZGYgKQp9CgojIyMjIyBHZW5lcmF0ZSBjdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiAoQ0RGKSBwbG90IGZvciBzZWxlY3RlZCBnZW5lLiBJZiBvcHRpb24gImFkZEJveFBsb3QiID0gVFJVRSwgdGhlbiBnZW5lcmF0ZSBhZGRpdGlvbmFsIGJveHBsb3QgYmVsb3cgdG8gc2hvdyB0aGUgZGF0YSB2YXJpYW5jZSBmb3Igc2VsZWN0ZWQgZ2VuZSBpbiBpbmRpdmlkdWFsIGdyb3VwcwpjZGZQbG90IDwtIGZ1bmN0aW9uKGdlbmUsIGRhdGEsIHRhcmdldHMsIHNhbXBsZU5hbWUsIGludF9jYW5jZXIsIGV4dF9jYW5jZXIsIGNvbXBfY2FuY2VyLCBhZGRfY2FuY2VyID0gTlVMTCwgYWRkQm94UGxvdCA9IEZBTFNFLCBzY2FsaW5nID0gImdlbmUtd2lzZSIsIHJlcG9ydF9kaXIpIHsKICAKICAjIyMjIyBSZW1vdmUgdGhlIGludGVybmFsIHJlZmVyZW5jZSBjb2hvcnQgZGF0YSBpZiB0aGUgcGF0aWVudCBzYW1wbGVzIG9yaWdpbnMgZnJvbSBvdGhlciB0aXNzdWUuIE9mIG5vdGUsIHRoZSBpbnRlcm5hbCByZWZlcmVuY2UgY29ob3J0IHdhcyBvbmx5IHVzZWQgdG8gcHJvY2VzcyB0aGUgaW4taG91c2UgZGF0YSAoaW5jbHVkaW5nIHRoZSBpbnZlc3RpZ2F0ZWQgcGF0aWVudCBzYW1wbGUpIGFuZCB0byBjb3JyZWN0IGJhdGNoLWVmZmVjdHMKICBpZiAoIGNvbXBfY2FuY2VyICE9IGludF9jYW5jZXIgKSB7CiAgICB0YXJnZXRzIDwtIHRhcmdldHNbIHRhcmdldHMkVGFyZ2V0ICUhaW4lIGludF9jYW5jZXIsIF0KICAgIGRhdGEgPC0gZGF0YVsgLHJvd25hbWVzKHRhcmdldHMpIF0KICB9CiAgCiAgIyMjIyMgSW5pdGlhdGUgbGlzdHMgd2l0aCBzdGF0cyBmb3IgZWFjaCBncm91cAogIHRhcmdldHMubGlzdCA8LSB1bmlxdWUodGFyZ2V0cyRUYXJnZXQpCiAgZ3JvdXAueiA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgodGFyZ2V0cy5saXN0KSkKICBuYW1lcyhncm91cC56KSA8LSB0YXJnZXRzLmxpc3QKICAKICAjIyMjIyAuLi4uIGFuZCBmb3Igc2VsZWN0ZWQgZ2VuZQogIGdyb3VwLnouZ2VuZSA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgodGFyZ2V0cy5saXN0KSkKICBuYW1lcyhncm91cC56LmdlbmUpIDwtIHRhcmdldHMubGlzdAoKICAjIyMjIyBHZXQgZXhwcmVzc2lvbi1yZWxhdGVkIHN0YXRzIGZvciBlYWNoIGdyb3VwCiAgIyMjIyMgLi4uIGZyb20gZ2VuZS13aXNlIGFwcHJvYWNoIAogIGlmICggc2NhbGluZyA9PSAiZ2VuZS13aXNlIiApIHsKCiAgICAjIyMjIyBHZXQgc3RhdHMgZm9yIGVhY2ggZ3JvdXAKICAgIGdlbmUuZGF0YSA8LSBkYXRhWyBnZW5lLCAsIGRyb3AgPSBGQUxTRV0KICAgIGdyb3VwLnouZ2VuZSA8LSBleHByR3JvdXBzU3RhdHNfZ2VuZVdpc2UoZ2VuZS5kYXRhLCB0YXJnZXRzKVtbMV1dCiAgICAKICAgICMjIyMjIC4uLiBhbmQgZm9yIGVhY2ggc2FtcGxlIGluIGluZGl2aWR1YWwgZ3JvdXBzCiAgICBnZW5lLnN0YXRzIDwtIGV4cHJHcm91cHNTdGF0c19nZW5lV2lzZShnZW5lLmRhdGEsIHRhcmdldHMpW1syXV0KCiAgICBmb3IgKCBncm91cCBpbiB0YXJnZXRzLmxpc3QgKSB7CiAgICAgICAgZ3JvdXAueltbIGdyb3VwXV0gPC0gY2JpbmQodChnZW5lLnN0YXRzW1sgZ3JvdXBdXSRtZWRpYW4pLCB0KGdlbmUuc3RhdHNbWyBncm91cF1dJHopLCB0KGdlbmUuc3RhdHNbWyBncm91cF1dJHEpLCB0KGdlbmUuc3RhdHNbWyBncm91cF1dJGN1bSkgKQogICAgICAgIGdyb3VwLnpbWyBncm91cF1dIDwtIGFzLmRhdGEuZnJhbWUoZ3JvdXAueltbIGdyb3VwXV0pCiAgICAgICAgY29sbmFtZXMoZ3JvdXAueltbIGdyb3VwXV0pIDwtIGMoIm1lZGlhbiIsICJ6IiwgInF1YW50aWxlIiwgImN1bSIpCiAgICB9CiAgICAKICAgIGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dIDwtIGRvLmNhbGwoInJiaW5kIiwgZ3JvdXAueikKICAgIAogICMjIyMjIC4uLiBvciBmcm9tIGdyb3VwLXdpc2UgYXBwcm9hY2gKICB9IGVsc2UgewogICAgZ3JvdXAueltbIHNhbXBsZU5hbWUgXV0gPC0gZXhwckdyb3VwU3RhdHNfZ3JvdXBXaXNlKGRhdGEsIHRhcmdldHMsIHNhbXBsZU5hbWUpCiAgICBncm91cC56W1sgZXh0X2NhbmNlciBdXSA8LSBleHByR3JvdXBTdGF0c19ncm91cFdpc2UoZGF0YSwgdGFyZ2V0cywgZXh0X2NhbmNlcikKICAgIAogICAgIyMjIyMgRXh0cmFjdCBleHByZXNzaW9uIGZvciBzZWxlY3RlZCBnZW5lcwogICAgZ3JvdXAuei5nZW5lW1sgc2FtcGxlTmFtZSBdXSA8LSBncm91cC56W1sgc2FtcGxlTmFtZSBdXVsgcm93bmFtZXMoZ3JvdXAueltbIHNhbXBsZU5hbWUgXV0pICVpbiUgZ2VuZSwgXQogICAgZ3JvdXAuei5nZW5lW1sgZXh0X2NhbmNlciBdXSA8LSBncm91cC56W1sgZXh0X2NhbmNlciBdXVsgcm93bmFtZXMoZ3JvdXAueltbIGV4dF9jYW5jZXIgXV0pICVpbiUgZ2VuZSwgXQogICAgCiAgICAjIyMjIyBBZGQgaW5mbyBmb3IgaW50ZXJuYWwgY29ob3J0CiAgICBpZiAoIGNvbXBfY2FuY2VyID09IGludF9jYW5jZXIgKSB7CiAgICAgIGdyb3VwLnpbWyBpbnRfY2FuY2VyIF1dIDwtIGV4cHJHcm91cFN0YXRzX2dyb3VwV2lzZShkYXRhLCB0YXJnZXRzLCBpbnRfY2FuY2VyKQogICAgICBncm91cC56LmdlbmVbWyBpbnRfY2FuY2VyIF1dIDwtIGdyb3VwLnpbWyBpbnRfY2FuY2VyIF1dWyByb3duYW1lcyhncm91cC56W1sgaW50X2NhbmNlciBdXSkgJWluJSBnZW5lLCBdCiAgICB9CiAgICAKICAgICMjIyMjIEFkZCBpbmZvIGZvciBhZGRpdGlvbmFsIGNhbmNlciB0eXBlIGlzIHNwZWNpZmllZAogICAgaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyKSApIHsKICAgICAgZ3JvdXAueltbIGFkZF9jYW5jZXIgXV0gPC0gZXhwckdyb3VwU3RhdHNfZ3JvdXBXaXNlKGRhdGEsIHRhcmdldHMsIGFkZF9jYW5jZXIpCiAgICAgIGdyb3VwLnouZ2VuZVtbIGFkZF9jYW5jZXIgXV0gPC0gZ3JvdXAueltbIGFkZF9jYW5jZXIgXV1bIHJvd25hbWVzKGdyb3VwLnpbWyBhZGRfY2FuY2VyIF1dKSAlaW4lIGdlbmUsIF0KICAgIH0KICB9CiAgCiAgIyMjIyMgR2VuZXJhdGUgYm94LXBsb3QgZm9yIHNlbGVjdGVkIGdlbmUKICBpZiAoIGFkZEJveFBsb3QgKSB7CiAgICAjIyMjIyBQZXJmb3JtIFotc2NvcmUgdHJhbnNmb3JtYXRpb24gb2YgdGhlIG1lZGlhbiBleHByZXNzaW9uIHZhbHVlcwogICAgaWYgKCBzY2FsaW5nID09ICJnZW5lLXdpc2UiICkgewogICAgICAKICAgICAgZGF0YS56IDwtIHQoc2NhbGUodChkYXRhKSkpCiAgICB9IGVsc2UgewogICAgICBkYXRhLnogPC0gc2NhbGUoZGF0YSwgc2NhbGUgPSBGQUxTRSkKICAgIH0KICAgIAogICAgdGFyZ2V0cyRUYXJnZXRbIHRhcmdldHMkVGFyZ2V0PT1zYW1wbGVOYW1lIF0gPC0gIlBhdGllbnQiCiAgICBnZW5lLmV4cHIuZGYgPC0gZGF0YS5mcmFtZSh0YXJnZXRzJFRhcmdldCwgZGF0YS56W2dlbmUsIF0pCiAgICBjb2xuYW1lcyhnZW5lLmV4cHIuZGYpIDwtIGMoIkdyb3VwIiwgIkV4cHJlc3Npb24iKQogICAgCiAgICAjIyMjIyBSZW9yZGVyIGdyb3VwcwogICAgaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyKSApIHsKICAgICAgZ2VuZS5leHByLmRmJEdyb3VwIDwtIGZhY3RvcihnZW5lLmV4cHIuZGYkR3JvdXAsIGxldmVscz1jKCBhZGRfY2FuY2VyLCBleHRfY2FuY2VyLCBpbnRfY2FuY2VyLCAiUGF0aWVudCIpKQogICAgICBncm91cC5jb2xvdXJzIDwtIGMoImZvcmVzdGdyZWVuIiwgImNvcm5mbG93ZXJibHVlIiwgInJlZCIsICJibGFjayIpCiAgICB9IGVsc2UgewogICAgICBnZW5lLmV4cHIuZGYkR3JvdXAgPC0gZmFjdG9yKGdlbmUuZXhwci5kZiRHcm91cCwgbGV2ZWxzPWMoZXh0X2NhbmNlciwgaW50X2NhbmNlciwgIlBhdGllbnQiKSkKICAgICAgZ3JvdXAuY29sb3VycyA8LSBjKCJjb3JuZmxvd2VyYmx1ZSIsICJyZWQiLCAiYmxhY2siKQogICAgfQogICAgCiAgICBwMiA8LSBwbG90X2x5KGdlbmUuZXhwci5kZiwgeD0gfkV4cHJlc3Npb24sIGNvbG9yID0gfkdyb3VwLCB0eXBlID0gJ2JveCcsIGppdHRlciA9IDAuMywgcG9pbnRwb3MgPSAwLCBib3hwb2ludHMgPSAnYWxsJywgY29sb3JzID0gZ3JvdXAuY29sb3Vycywgb3BhY2l0eSA9IDAuNSwgb3JpZW50YXRpb24gPSAnaCcsIHdpZHRoID0gODAwLCBoZWlnaHQgPSA0MDAsIHNob3dsZWdlbmQ9RkFMU0UpCiAgfQogIAogICMjIyMjIEdlbmVyYXRlIGludGVyYWN0aXZlIENERiBwbG90IHdpdGggcGxvdGx5CiAgIyMjIyMgSW5jbHVkZSB0aGUgaW50ZXJuYWwgcmVmZXJlbmNlIGNvaG9ydCBpbiB0aGUgcGxvdAogIGlmICggY29tcF9jYW5jZXIgPT0gaW50X2NhbmNlciApIHsKICAgIHAxIDwtIHBsb3RfbHkoZ3JvdXAueltbIHNhbXBsZU5hbWUgXV0sIHggPSB+eiwgY29sb3IgPSBJKCJibGFjayIpLCB3aWR0aCA9IDcwMCwgaGVpZ2h0ID0gMjAwKSAlPiUKICAgIAogICAgICAjIyMjIyBBZGQgc2FtcGxlIGRhdGEKICAgICAgYWRkX21hcmtlcnMoeSA9IGdyb3VwLnouZ2VuZVtbIHNhbXBsZU5hbWUgXV0kcXVhbnRpbGUsIHggPSBncm91cC56LmdlbmVbWyBzYW1wbGVOYW1lIF1dJHosCiAgICAgICAgICAgICAgICAgIHRleHQgPSByb3duYW1lcyhncm91cC56LmdlbmVbWyBzYW1wbGVOYW1lIF1dICksCiAgICAgICAgICAgICAgICAgIG5hbWUgPSAiUGF0aWVudCIsCiAgICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDEyLCBjb2xvciA9ICJibGFjayIpLAogICAgICAgICAgICAgICAgICBzaG93bGVnZW5kID0gVFJVRSkgJT4lCiAgICAKICAgICAgYWRkX2xpbmVzKHkgPSBncm91cC56W1sgc2FtcGxlTmFtZSBdXSRxdWFudGlsZSwgeCA9IGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dJHosIAogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JleSIpLAogICAgICAgICAgICAgICAgdGV4dCA9IHJvd25hbWVzKCBncm91cC56W1sgc2FtcGxlTmFtZSBdXSApLAogICAgICAgICAgICAgICAgbmFtZSA9ICJQYXRpZW50Iiwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAgICAgICAKICAgICAgIyMjIyMgQWRkIGludF9jYW5jZXIgZGF0YQogICAgICBhZGRfbWFya2Vycyh5ID0gZ3JvdXAuei5nZW5lW1sgaW50X2NhbmNlciBdXSRxdWFudGlsZSwgeCA9ICBncm91cC56LmdlbmVbWyBpbnRfY2FuY2VyIF1dJHosCiAgICAgICAgICAgICAgICAgIHRleHQgPSByb3duYW1lcyggZ3JvdXAuei5nZW5lW1sgaW50X2NhbmNlciBdXSksCiAgICAgICAgICAgICAgICAgIG5hbWUgPSBpbnRfY2FuY2VyLAogICAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSAxMiwgb3BhY2l0eSA9IDAuNSwgY29sb3IgPSAicmVkIiksCiAgICAgICAgICAgICAgICAgIHNob3dsZWdlbmQgPSBUUlVFKSAlPiUKICAgIAogICAgICBhZGRfbGluZXMoeSA9IGdyb3VwLnpbWyBpbnRfY2FuY2VyIF1dJHF1YW50aWxlLCB4ID0gZ3JvdXAueltbIGludF9jYW5jZXIgXV0keiwgb3BhY2l0eSA9IDAuNSwKICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gInJlZCIsIGRhc2ggPSAiZGFzaCIpLAogICAgICAgICAgICAgICAgdGV4dCA9IHJvd25hbWVzKCBncm91cC56W1sgaW50X2NhbmNlciBdXSApLAogICAgICAgICAgICAgICAgbmFtZSA9IGludF9jYW5jZXIsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgICAgICAgICAKICAgICAgIyMjIyMgQWRkIGV4dF9jYW5jZXIgZGF0YQogICAgICBhZGRfbWFya2Vycyh5ID0gZ3JvdXAuei5nZW5lW1sgZXh0X2NhbmNlciBdXSRxdWFudGlsZSwgeCA9ICBncm91cC56LmdlbmVbWyBleHRfY2FuY2VyIF1dJHosCiAgICAgICAgICAgICAgICAgIHRleHQgPSByb3duYW1lcyggZ3JvdXAuei5nZW5lW1sgZXh0X2NhbmNlciBdXSApLAogICAgICAgICAgICAgICAgICBuYW1lID0gZXh0X2NhbmNlciwKICAgICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gMTIsIG9wYWNpdHkgPSAwLjUsIGNvbG9yID0gImNvcm5mbG93ZXJibHVlIiksCiAgICAgICAgICAgICAgICAgIHNob3dsZWdlbmQgPSBUUlVFKSAlPiUKICAgIAogICAgICBhZGRfbGluZXMoeSA9IGdyb3VwLnpbWyBleHRfY2FuY2VyIF1dJHF1YW50aWxlLCB4ID0gZ3JvdXAueltbIGV4dF9jYW5jZXIgXV0keiwgb3BhY2l0eSA9IDAuNSwKICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImNvcm5mbG93ZXJibHVlIiwgZGFzaCA9ICJkYXNoIiksCiAgICAgICAgICAgICAgICB0ZXh0ID0gcm93bmFtZXMoIGdyb3VwLnpbWyBleHRfY2FuY2VyIF1dICksCiAgICAgICAgICAgICAgICBuYW1lID0gZXh0X2NhbmNlciwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAgICAgCiAgICAgICMjIyMjIEFkZCBxdWFudGlsZSBsaW5lcwogICAgICBhZGRfbGluZXMoeSA9IHNlcSgwLDEwMCwxMCksIHggPSByZXAocXVhbnRpbGUoZ3JvdXAueltbIHNhbXBsZU5hbWUgXV0keilbMl0sIDExKSwgb3BhY2l0eSA9IDAuNSwKICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImdyYXkiLCBkYXNoID0gImRhc2giKSwKICAgICAgICAgICAgICAgIG5hbWUgPSAiUTEiLCBzaG93bGVnZW5kID0gRkFMU0UpICU+JQogICAgICAKICAgICAgYWRkX2xpbmVzKHkgPSBzZXEoMCwxMDAsMTApLCB4ID0gcmVwKHF1YW50aWxlKGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dJHopWzNdLCAxMSksIG9wYWNpdHkgPSAwLjUsCiAgICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJncmF5IiwgZGFzaCA9ICJkYXNoIiksCiAgICAgICAgICAgICAgICBuYW1lID0gIlEyIiwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAgICAgCiAgICAgIGFkZF9saW5lcyh5ID0gc2VxKDAsMTAwLDEwKSwgeCA9IHJlcChxdWFudGlsZShncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6KVs0XSwgMTEpLCBvcGFjaXR5ID0gMC41LAogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIGRhc2ggPSAiZGFzaCIpLAogICAgICAgICAgICAgICAgbmFtZSA9ICJRMyIsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lIAogICAgICAKICAgICAgICAgIGxheW91dCh0aXRsZSA9IGdlbmUsIHhheGlzID0gbGlzdCh0aXRsZSA9ICJtUk5BIGV4cHJlc3Npb24gKFotc2NvcmUpIiwgemVyb2xpbmUgPSBGQUxTRSwgcmFuZ2UgPSBjKG1pbihncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6KS0xLjUsIG1heChncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6KSsxLjUpKSwKICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJQZXJjZW50aWxlIiksCiAgICAgICAgICAgICBsZWdlbmQgPSBsaXN0KG9yaWVudGF0aW9uID0gJ3YnLCB4ID0gMC4wMiwgeSA9IDEsIGJnY29sb3IgPSAid2hpdGUiKQogICAgICApCiAgCiAgIyMjIyMgU2tpcCB0aGUgaW50ZXJuYWwgcmVmZXJlbmNlIGNvaG9ydCBpbiB0aGUgcGxvdAogIH0gZWxzZSB7CiAgICBwMSA8LSBwbG90X2x5KGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dLCB4ID0gfnosIGNvbG9yID0gSSgiYmxhY2siKSwgd2lkdGggPSA3MDAsIGhlaWdodCA9IDIwMCkgJT4lCiAgCiAgICAjIyMjIyBBZGQgc2FtcGxlIGRhdGEKICAgIGFkZF9tYXJrZXJzKHkgPSBncm91cC56LmdlbmVbWyBzYW1wbGVOYW1lIF1dJHF1YW50aWxlLCB4ID0gZ3JvdXAuei5nZW5lW1sgc2FtcGxlTmFtZSBdXSR6LAogICAgICAgICAgICAgICAgdGV4dCA9IHJvd25hbWVzKGdyb3VwLnouZ2VuZVtbIHNhbXBsZU5hbWUgXV0gKSwKICAgICAgICAgICAgICAgIG5hbWUgPSAiUGF0aWVudCIsCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwKICAgICAgICAgICAgICAgIHNob3dsZWdlbmQgPSBUUlVFKSAlPiUKICAKICAgIGFkZF9saW5lcyh5ID0gZ3JvdXAueltbIHNhbXBsZU5hbWUgXV0kcXVhbnRpbGUsIHggPSBncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6LCAKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJncmV5IiksCiAgICAgICAgICAgICAgdGV4dCA9IHJvd25hbWVzKCBncm91cC56W1sgc2FtcGxlTmFtZSBdXSApLAogICAgICAgICAgICAgIG5hbWUgPSAiUGF0aWVudCIsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgICAgICAgCiAgICAjIyMjIyBBZGQgZXh0X2NhbmNlciBkYXRhCiAgICBhZGRfbWFya2Vycyh5ID0gZ3JvdXAuei5nZW5lW1sgZXh0X2NhbmNlciBdXSRxdWFudGlsZSwgeCA9ICBncm91cC56LmdlbmVbWyBleHRfY2FuY2VyIF1dJHosCiAgICAgICAgICAgICAgICB0ZXh0ID0gcm93bmFtZXMoIGdyb3VwLnouZ2VuZVtbIGV4dF9jYW5jZXIgXV0gKSwKICAgICAgICAgICAgICAgIG5hbWUgPSBleHRfY2FuY2VyLAogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gMTIsIG9wYWNpdHkgPSAwLjUsIGNvbG9yID0gImNvcm5mbG93ZXJibHVlIiksCiAgICAgICAgICAgICAgICBzaG93bGVnZW5kID0gVFJVRSkgJT4lCiAgCiAgICBhZGRfbGluZXMoeSA9IGdyb3VwLnpbWyBleHRfY2FuY2VyIF1dJHF1YW50aWxlLCB4ID0gZ3JvdXAueltbIGV4dF9jYW5jZXIgXV0keiwgb3BhY2l0eSA9IDAuNSwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJjb3JuZmxvd2VyYmx1ZSIsIGRhc2ggPSAiZGFzaCIpLAogICAgICAgICAgICAgIHRleHQgPSByb3duYW1lcyggZ3JvdXAueltbIGV4dF9jYW5jZXIgXV0gKSwKICAgICAgICAgICAgICBuYW1lID0gZXh0X2NhbmNlciwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAgIAogICAgIyMjIyMgQWRkIHF1YW50aWxlIGxpbmVzCiAgICBhZGRfbGluZXMoeSA9IHNlcSgwLDEsMC4xKSwgeCA9IHJlcChxdWFudGlsZShncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6KVsyXSwgMTEpLCBvcGFjaXR5ID0gMC41LAogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImdyYXkiLCBkYXNoID0gImRhc2giKSwKICAgICAgICAgICAgICBuYW1lID0gIlExIiwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAgIAogICAgYWRkX2xpbmVzKHkgPSBzZXEoMCwxLDAuMSksIHggPSByZXAocXVhbnRpbGUoZ3JvdXAueltbIHNhbXBsZU5hbWUgXV0keilbM10sIDExKSwgb3BhY2l0eSA9IDAuNSwKICAgICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICJncmF5IiwgZGFzaCA9ICJkYXNoIiksCiAgICAgICAgICAgICAgbmFtZSA9ICJRMiIsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lCiAgICAKICAgIGFkZF9saW5lcyh5ID0gc2VxKDAsMSwwLjEpLCB4ID0gcmVwKHF1YW50aWxlKGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dJHopWzRdLCAxMSksIG9wYWNpdHkgPSAwLjUsCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JheSIsIGRhc2ggPSAiZGFzaCIpLAogICAgICAgICAgICAgIG5hbWUgPSAiUTMiLCBzaG93bGVnZW5kID0gRkFMU0UpICU+JSAKICAgIAogICAgICAgIGxheW91dCh0aXRsZSA9IGdlbmUsIHhheGlzID0gbGlzdCh0aXRsZSA9ICJtUk5BIGV4cHJlc3Npb24gKFotc2NvcmUpIiwgemVyb2xpbmUgPSBGQUxTRSwgcmFuZ2UgPSBjKG1pbihncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6KS0xLjUsIG1heChncm91cC56W1sgc2FtcGxlTmFtZSBdXSR6KSsxLjUpKSwKICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiUGVyY2VudGlsZSIpLAogICAgICAgICAgIGxlZ2VuZCA9IGxpc3Qob3JpZW50YXRpb24gPSAndicsIHggPSAwLjAyLCB5ID0gMSwgYmdjb2xvciA9ICJ3aGl0ZSIpCiAgICApCiAgfQogIAogICMjIyMjIENvbWJpbmUgQ0RGIHBsb3Qgd2l0aCBib3hwbG90IGlmIHRoaXMgb3B0aW9uIGlzIHNlbGVjdGVkCiAgaWYgKCBhZGRCb3hQbG90ICkgewogICAgcDFfMiA8LSBzdWJwbG90KHAxLCBwMiwgbnJvd3MgPSAyLCBzaGFyZVggPSBUUlVFLCBzaGFyZVkgPSBGQUxTRSwgdGl0bGVZID0gVFJVRSwgaGVpZ2h0cyA9IGMoMC43LCAwLjMpKSAlPiUKICBsYXlvdXQoeGF4aXMgPSBsaXN0KHRpdGxlID0gIm1STkEgZXhwcmVzc2lvbiAoWi1zY29yZSkiLCB6ZXJvbGluZSA9IEZBTFNFLCByYW5nZSA9IGMobWluKGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dJHopLTEuNSwgbWF4KGdyb3VwLnpbWyBzYW1wbGVOYW1lIF1dJHopKzEuNSkpLAogICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlBlcmNlbnRpbGUiKSwKICAgICAgICAgIGxlZ2VuZCA9IGxpc3Qob3JpZW50YXRpb24gPSAndicsIHggPSAwLjAyLCB5ID0gMSwgYmdjb2xvciA9ICJ3aGl0ZSIpLAogICAgICAgICAgeWF4aXMyID0gbGlzdCggdGl0bGUgPSIiKSwgeGF4aXMyID0gbGlzdCh0aXRsZSA9IHBhc3RlMChnZW5lLCAiIG1STkEgZXhwcmVzc2lvbiAoWi1zY29yZSkiKSksIG1hcmdpbiA9IGxpc3QobD01MCwgcj01MCwgYj01MCwgdD01MCwgcGFkPTQpLCBhdXRvc2l6ZSA9IEZBTFNFLAogICAgICAgICBzaG93bGVnZW5kPVRSVUUsIHNob3dsZWdlbmQyPUZBTFNFKQogICAgCiAgICByZXR1cm4oIHAxXzIgKQogICAgCiAgfSBlbHNlIHsKICAgIHJldHVybiggcDEgKQogIH0KICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UKICBybShnZW5lLCB0YXJnZXRzLCBkYXRhLCBzYW1wbGVOYW1lLCB0YXJnZXRzLmxpc3QsIGdyb3VwLnosIGdyb3VwLnouZ2VuZSwgZ2VuZS5kYXRhLCBnZW5lLnN0YXRzLCBkYXRhLnosIGdlbmUuZXhwci5kZiwgZ3JvdXAuY29sb3VycykKICAKICAjIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKfQoKIyMjIyMgQ29udmVydCBkZW5zaXR5IHRvIGNvdW50cwpkZW5zaXR5MmZyZXEgPC0gZnVuY3Rpb24oZGVuc2l0eSkgewogIGZyZXEgPSBsZW5ndGgoZGVuc2l0eSkvc3VtKGRlbnNpdHkpICogZGVuc2l0eQogIHJldHVybihmcmVxKQp9CgojIyMjIyBHZW5lcmF0ZSBkZW5zaXR5IGFuZCBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbiBwbG90cyBmb3Igc2VsZWN0ZWQgZ2VuZSwgaGlnaGxpZ2h0aW5nIHNhbXBsZXMgb2YgaW50ZXJlc3QKZGVuc2l0eVBsb3QgPC0gZnVuY3Rpb24oZ2VuZSwgZGF0YSwgbWFpbl90aXRsZSwgeF90aXRsZSwgc2FtcGxlTmFtZSwgZGlzdHJpYnV0aW9ucyA9IE5VTEwsIHNjYWxpbmcgPSAiZ2VuZS13aXNlIikgewogIAogIGlmICggc2NhbGluZyA9PSAiZ2VuZS13aXNlIiApIHsKICAgIGRhdGEueiA8LSB0KHNjYWxlKHQoZGF0YSkpKQogIH0gZWxzZSB7CiAgICBkYXRhLnogPC0gc2NhbGUoZGF0YSwgc2NhbGUgPSBGQUxTRSkKICB9CiAgCiAgIyMjIyMgVXNlZCBkYXRhIGZvciB1c2VyLWRlZmluZWQgZ2VuZXMKICBkYXRhLnogPC0gZGF0YS56WyBnZW5lLCAsZHJvcD1GQUxTRV0KCiAgIyMjIyMgQ3JlYXRlIGRhdGEgZnJhbWUgYW5kIGZpbGwgaXQgd2l0aCBleHByZXNzaW9uIGFuZCBkZW5zaXR5IHZhbHVlcyBmb3IgZWFjaCBzYW1wbGUgZm9yIHNlbGVjdGVkIGdlbmUKICBkYXRhLmRmIDwtIGRhdGEuZnJhbWUoZ2VuZSA9ICJPYnNlcnZlZCBkaXN0cmlidXRpb24iLCBzYW1wbGUgPSBjb2xuYW1lcyhkYXRhLnopW29yZGVyKGRhdGEueildLCBleHByID0gc29ydChkYXRhLnopLCBkZW5zID0gZGVuc2l0eTJmcmVxKGRlbnNpdHkoZGF0YS56LCBuPW5jb2woZGF0YS56KSkkeSkpCiAgCiAgIyMjIyMgR2VuZXJhdGUgdmFsdWVzIHRvIGdlbmVyYXRlIHZhcmlvdXMgZGlzdHJpYnV0aW9ucwogIGlmICggIWlzLm51bGwoZGlzdHJpYnV0aW9ucykgKSB7CiAgICAKICAgICMjIyMjIFVzZSB0aGUgZGVuc2l0eSB2YWx1ZXMgb2J0YWluZWQgZnJvbSB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMKICAgIGV4cHIuc29ydGVkIDwtIHNvcnQoZGF0YS56KQogICAgCiAgICAjIyMjIyBHZXQgbWluIGFuZCBtYXggdmFsdWVzIGJhc2VkIG9uIHRoZSBleHByZXNzaW9uIGRhdGEKICAgIGRhdGEueCA8LSBzZXEobWluKGV4cHIuc29ydGVkKSwgbWF4KGV4cHIuc29ydGVkKSwgbGVuZ3RoLm91dCA9IG5jb2woZGF0YS56KSkKICAgIAogICAgIyMjIyMgQ3JlYXRlIGVtcHR5IGRhdGEgZnJhbWUKICAgIGRhdGEuZGYuZGlzdCA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sID0gNCwgbnJvdyA9IDApKQogICAgY29sbmFtZXMoZGF0YS5kZi5kaXN0KSA8LSBjKCJnZW5lIiwgInNhbXBsZSIsICJleHByIiwgImRlbnMiKQogICAgCiAgICAjIyMjIyBHZW5lcmF0ZSB5LXZhbHVlcyB0byBtaXJyb3IgZGlzdHJpYnV0aW9ucyBvZiBpbnRlcmVzdAogICAgIyMjIyMgR2VuZXJhdGUgeS12YWx1ZXMgZm9yIG5vcm1hbCBkaXN0cmlidXRpb24uIFVzZWZ1bCByZXNvdXJjZSBodHRwczovL3N0YXRzLmlkcmUudWNsYS5lZHUvci9tb2R1bGVzL3Byb2JhYmlsaXRpZXMtYW5kLWRpc3RyaWJ1dGlvbnMvCiAgICBpZiAoICJub3JtYWwiICVpbiUgdG9sb3dlcihkaXN0cmlidXRpb25zKSApIHsKICAgICAgZGF0YS55IDwtIGRub3JtKGRhdGEueCwgbWVhbiA9IG1lYW4oZGF0YS54KSwgc2QgPSAobWF4KGRhdGEueCktbWVhbihkYXRhLngpKS81KQogICAgICBkYXRhLmRmLmRpc3QgPC0gcmJpbmQoZGF0YS5kZi5kaXN0LCBkYXRhLmZyYW1lKGdlbmU9Ik5vcm1hbCBkaXN0cmlidXRpb24iLCBzYW1wbGUgPSBjb2xuYW1lcyhkYXRhLnopW29yZGVyKGRhdGEueildLCBleHByPWRhdGEueCwgZGVucz1kZW5zaXR5MmZyZXEoZGF0YS55KSkpCiAgICB9IAogICAgCiAgICAjIyMjIyBHZW5lcmF0ZSB4LSBhbmQgeS12YWx1ZXMgZm9yIGJpbm9taWFsIGRpc3RyaWJ1dGlvbi4gVXNlZnVsIGxpbmsgaHR0cHM6Ly9zdGF0LmV0aHouY2gvUi1tYW51YWwvUi1kZXZlbC9saWJyYXJ5L3N0YXRzL2h0bWwvQmlub21pYWwuaHRtbAogICAgaWYgKCAiYmlub21pYWwiICVpbiUgdG9sb3dlcihkaXN0cmlidXRpb25zKSApIHsKICAgICAgZGF0YS54IDwtIDE6bmNvbChkYXRhLnopCiAgICAgIGRhdGEueSA8LSBkYmlub20oZGF0YS54LCBuY29sKGRhdGEueiksIDAuMjUpCiAgICAgIGRhdGEueCA8LSByZXNjYWxlKGRhdGEueCwgdG8gPSBjKG1pbihleHByLnNvcnRlZCksIG1heChleHByLnNvcnRlZCkpKQogICAgICBkYXRhLmRmLmRpc3QgPC0gcmJpbmQoZGF0YS5kZi5kaXN0LCBkYXRhLmZyYW1lKGdlbmU9IkJpbm9taWFsIGRpc3RyaWJ1dGlvbiAocD0wLjI1KSIsIHNhbXBsZSA9IGNvbG5hbWVzKGRhdGEueilbb3JkZXIoZGF0YS56KV0sIGV4cHI9ZGF0YS54LCBkZW5zPWRlbnNpdHkyZnJlcShkYXRhLnkpKSkKICAgICAgCiAgICAgIGRhdGEueCA8LSAxOm5jb2woZGF0YS56KQogICAgICBkYXRhLnkgPC0gZGJpbm9tKGRhdGEueCwgbmNvbChkYXRhLnopLCAwLjc1KQogICAgICBkYXRhLnggPC0gcmVzY2FsZShkYXRhLngsIHRvID0gYyhtaW4oZXhwci5zb3J0ZWQpLCBtYXgoZXhwci5zb3J0ZWQpKSkKICAgICAgZGF0YS5kZi5kaXN0IDwtIHJiaW5kKGRhdGEuZGYuZGlzdCwgZGF0YS5mcmFtZShnZW5lPSJCaW5vbWlhbCBkaXN0cmlidXRpb24gKHA9MC43NSkiLCBzYW1wbGUgPSBjb2xuYW1lcyhkYXRhLnopW29yZGVyKGRhdGEueildLCBleHByPWRhdGEueCwgZGVucz1kZW5zaXR5MmZyZXEoZGF0YS55KSkpCiAgICB9CiAgICAKICAgICMjIyMjIERyYXcgbi8yIHNhbXBsZXMgZnJvbSBhIG5vcm1hbCBkaXN0cmlidXRpb25zIHdpdGggb25lIG1lZGlhbiBhbmQgYW5vdGhlciBuLzIgc2FtcGxlcyBmcm9tIGEgc2Vjb25kIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBhIGRpZmZlcmVudCBtZWRpYW4uIFVzZWZ1bCBsaW5rICAgICAgICAgICAgICAgICAgaHR0cHM6Ly9zdGF0cy5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvMzU1MzQ0L3NpbXVsYXRpbmctYS1iaW1vZGFsLWRpc3RyaWJ1dGlvbi1pbi10aGUtcmFuZ2Utb2YtMTUtaW4tcgogICAgaWYgKCAiYmltb2RhbCIgJWluJSB0b2xvd2VyKGRpc3RyaWJ1dGlvbnMpICl7CiAgICAgIGRhdGEueDEgPC0gc2VxKG1pbihleHByLnNvcnRlZCksIG1lZGlhbihleHByLnNvcnRlZCksIGxlbmd0aC5vdXQgPSBuY29sKGRhdGEueikvMikKICAgICAgZGF0YS54MiA8LSBzZXEobWVkaWFuKGV4cHIuc29ydGVkKSwgbWF4KGV4cHIuc29ydGVkKSwgbGVuZ3RoLm91dCA9IG5jb2woZGF0YS56KS8yKQogICAgICAKICAgICAgIyMjIyMgQ29tYmluZSBib3RoIG5vcm1hbCBkaXN0cmlidXRpb25zIHRvIGdlbmVyYXRlIGEgYmltb2RhbCBkaXN0cmlidXRpb24uIE1ha2Ugc3VyZSB0aGUgdGhlIGxlbmd0aCBvZiB0aGlzIHZlY3RvciBpcyBlcXVhbCB0byB0aGUgbnVtYmVyIHNhbXBsZXMgaW4gdGhlIGRhdGEKICAgICAgZGF0YS54IDwtIGMoZGF0YS54MSwgZGF0YS54MikKICAgICAgZGF0YS54IDwtIGRhdGEueFsxOm5jb2woZGF0YS56KV0KICAgICAgCiAgICAgICMjIyMjIEdlbmVyYXRlIHktdmFsdWVzIGZvciBiaW1vZGFsIGRpc3RyaWJ1dGlvbgogICAgICBkYXRhLnkgPC0gYyhkbm9ybShkYXRhLngxLCBtZWFuID0gbWVhbihkYXRhLngxKSwgc2QgPSAobWF4KGRhdGEueDEpLW1lYW4oZGF0YS54MSkpLzMpLCBkbm9ybShkYXRhLngyLCBtZWFuID0gbWVhbihkYXRhLngyKSwgc2QgPSAobWF4KGRhdGEueDIpLW1lYW4oZGF0YS54MikpLzMpKQogICAgICBkYXRhLnkgPC0gZGF0YS55WzE6bmNvbChkYXRhLnopXQogICAgICAKICAgICAgIyMjIyMgQWRkIGJpbW9kYWwgZGlzdCB2YWx1ZXMgdG8gdGhlIGRpc3RyaWJ1dGlvbiBkYXRhZnJhbWUKICAgICAgZGF0YS5kZi5kaXN0IDwtIHJiaW5kKGRhdGEuZGYuZGlzdCwgZGF0YS5mcmFtZShnZW5lID0gIkJpbW9kYWwgZGlzdHJpYnV0aW9uIiwgc2FtcGxlID0gY29sbmFtZXMoZGF0YS56KVtvcmRlcihkYXRhLnopXSwgZXhwciA9IGRhdGEueCwgZGVucyA9IGRlbnNpdHkyZnJlcShkYXRhLnkpKSkKICAgIH0KICAgIAogICAgZGF0YS5kZiA8LSByYmluZChkYXRhLmRmLCBkYXRhLmRmLmRpc3QpCiAgICAKICAgICMjIyMjIEV4dHJhY3QgZXhwcmVzc2lvbiBmb3Igc2VsZWN0ZWQgc2FtcGxlIGluIHRoZSBkaXN0cmlidXRpb25zIGRhdGFmcmFtZQogICAgZGF0YS5kZi5zZWxlY3RlZCA8LSBkYXRhLmRmWyBzYW1wbGVOYW1lID09IGRhdGEuZGYkc2FtcGxlLCBdCiAgfQogIAogICMjIyMjIEdldCBtaW4gYW5kIG1heCB2YWx1ZXMgYmFzZWQgb24gdGhlIGV4cHJlc3Npb24gZGF0YQogIGRlbi54IDwtIHNvcnQoZGF0YS5kZiRleHByKQogIGRlbi55IDwtIHNvcnQoZGF0YS5kZiRkZW5zKQogIAogICMjIyMjIEFzc2lnbiBjb2xvdXJzIHRvIGRpc3RyaWJ1dGlvbnMKICBnZW5lcy5jb2xvdXIgPC0gZ2V0Q29sb3VycyhyZXYodW5pcXVlKGRhdGEuZGYkZ2VuZSkpKQogIAogICMjIyMjIEdlbmVyYXRlIGludGVyYWN0aXZlIGRlbnNpdHkgcGxvdAogIHAgPC0gcGxvdF9seShkYXRhLmRmLCB4ID0gfmV4cHIsIHkgPSB+ZGVucywgdHlwZSA9ICdzY2F0dGVyJywgbW9kZSA9ICdsaW5lcycsIGNvbG9yID0gfmdlbmUsIGNvbG9ycyA9IGdlbmVzLmNvbG91cltbMV1dLCB3aWR0aCA9IDc1MCwgaGVpZ2h0ID0gMjAwKSAlPiUKICAgIGFkZF9tYXJrZXJzKHkgPSBkYXRhLmRmLnNlbGVjdGVkJGRlbnMsIHggPSBkYXRhLmRmLnNlbGVjdGVkJGV4cHIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJQYXRpZW50IiwKICAgICAgICAgICAgICAgIHRleHQgPSAiUGF0aWVudCIsCiAgICAgICAgICAgICAgICBtb2RlID0gJ21hcmtlcnMnLAogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChzaXplID0gOCwgY29sb3JzID0gZGF0YS5kZi5zZWxlY3RlZCRzYW1wbGUsIGNvbG9yID0gcmVwKEkoImJsYWNrIiksIGVhY2ggPSBucm93KGRhdGEuZGYuc2VsZWN0ZWQpKSwgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JleSIsIHdpZHRoID0gMikpLAogICAgICAgICAgICAgICAgc2hvd2xlZ2VuZCA9IFRSVUUsCiAgICAgICAgICAgICAgICBpbmhlcml0ID0gRkFMU0UpICU+JQogICAgIGxheW91dCh0aXRsZSA9IG1haW5fdGl0bGUsCiAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0geF90aXRsZSwgcmFuZ2UgPSBjKGRlbi54WzFdLGRlbi54W2xlbmd0aChkZW4ueCldKSksCiAgICAgICAgICAgeWF4aXMgPSBsaXN0ICh0aXRsZSA9ICdXZWlnaHQnLCByYW5nZSA9IGMoZGVuLnlbMV0sZGVuLnlbbGVuZ3RoKGRlbi55KV0pLCBzaWRlID0gInJpZ2h0IiksCiAgICAgICAgICAgbGVnZW5kID0gbGlzdChvcmllbnRhdGlvbiA9ICdoJywgeSA9IDEuMykpCiAgCiAgcmV0dXJuKCBwICkKICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UKICBybShnZW5lLCBleHByLnNvcnRlZCkKICBybShsaXN0ID0gbHMocGF0dGVybj0nXmRhdGEqJykpCiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCn0KCiMjIyMjIEdlbmVyYXRlIGJveC1wbG90IGZvciBzZWxlY3RlZCBnZW5lcywgaGlnaGxpZ2h0aW5nIHNhbXBsZXMgb2YgaW50ZXJlc3QKYmFyUGxvdCA8LSBmdW5jdGlvbihnZW5lLCBkYXRhLCB0YXJnZXRzLCB5X3RpdGxlID0gIkNvdW50cyIsIHNhbXBsZU5hbWUsICBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBOVUxMICkgewoKICAjIyMjIyBVc2VkIGRhdGEgZm9yIHVzZXItZGVmaW5lZCBnZW5lcwogIGRhdGEgPC0gZGF0YVsgZ2VuZSwgLGRyb3A9RkFMU0VdCiAgCiAgIyMjIyMgUHJlcGFyZSBkYXRhIGZyYW1lCiAgdGFyZ2V0cyRUYXJnZXRbIHRhcmdldHMkVGFyZ2V0PT1zYW1wbGVOYW1lIF0gPC0gIlBhdGllbnQiCiAgcm93bmFtZXModGFyZ2V0cylbIHJvd25hbWVzKHRhcmdldHMpPT1zYW1wbGVOYW1lIF0gPC0gIlBhdGllbnQiCiAgZGF0YS5kZiA8LSBkYXRhLmZyYW1lKHRhcmdldHMkVGFyZ2V0LCByb3duYW1lcyh0YXJnZXRzKSwgYXMubnVtZXJpYyhkYXRhKSkKICBjb2xuYW1lcyhkYXRhLmRmKSA8LSBjKCJHcm91cCIsIlNhbXBsZSIsICJEYXRhIikKICAKICAjIyMjIyBSZW9yZGVyIGdyb3VwcyBhbmQgYWRkIGNvbG91cnMKICBpZiAoICFpcy5udWxsKGFkZF9jYW5jZXIpICkgewogICAgZGF0YS5kZiRHcm91cCA8LSBmYWN0b3IoZGF0YS5kZiRHcm91cCwgbGV2ZWxzPWMoIGFkZF9jYW5jZXIsIGV4dF9jYW5jZXIsIGludF9jYW5jZXIsICJQYXRpZW50IikpCiAgICBncm91cC5jb2xvdXJzIDwtIGMoImZvcmVzdGdyZWVuIiwgImNvcm5mbG93ZXJibHVlIiwgInJlZCIsICJibGFjayIpCiAgfSBlbHNlIHsKICAgIGRhdGEuZGYkR3JvdXAgPC0gZmFjdG9yKGRhdGEuZGYkR3JvdXAsIGxldmVscz1jKGV4dF9jYW5jZXIsIGludF9jYW5jZXIsICJQYXRpZW50IikpCiAgICBncm91cC5jb2xvdXJzIDwtIGMoImNvcm5mbG93ZXJibHVlIiwgInJlZCIsICJibGFjayIpCiAgfQogIAogICMjIyMjIFRoZSBkZWZhdWx0IG9yZGVyIHdpbGwgYmUgYWxwaGFiZXRpemVkIHVubGVzcyBzcGVjaWZpZWQgYXMgYmVsb3cKICBkYXRhLmRmJFNhbXBsZSA8LSBmYWN0b3IoZGF0YS5kZiRTYW1wbGUsIGxldmVscyA9IGRhdGEuZGZbWyJTYW1wbGUiXV0pCiAgcCA8LSBwbG90X2x5KGRhdGEuZGYsIHggPSB+U2FtcGxlLCB5ID0gfkRhdGEsIGNvbG9yID0gfkdyb3VwLCB0eXBlID0gJ2JhcicsIGNvbG9ycyA9IGdyb3VwLmNvbG91cnMsIHdpZHRoID0gNzUwLCBoZWlnaHQgPSAyMDApICU+JQogICAgbGF5b3V0KHRpdGxlID0gIiIsIHhheGlzID0gbGlzdCggdGl0bGUgPSAiIiwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSksIHlheGlzID0gbGlzdCh0aXRsZSA9IHlfdGl0bGUpLCBhdXRvc2l6ZSA9IEYsIGxlZ2VuZCA9IGxpc3Qob3JpZW50YXRpb24gPSAnaCcsIHkgPSAxLjIpLCBzaG93bGVnZW5kPVRSVUUpCiAgCiAgcmV0dXJuKCBwICkKICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UKICBybShsaXN0ID0gbHMocGF0dGVybj0nXmRhdGEqJykpCiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCn0KCiMjIyMjIEdlbmVyYXRlIGJveHBsb3QgcHJlc2VudGluZyBleHByZXNzaW9uIHByb2ZpbGVzIGZvciBzZWxlY3RlZCBzZXQgb2YgZ2VuZXMKZ2xhbmNlRXhwclBsb3QgPC0gZnVuY3Rpb24oZ2VuZXMsIGRhdGEsIHRhcmdldHMsIHNhbXBsZU5hbWUsIGludF9jYW5jZXIsIGV4dF9jYW5jZXIsIGNvbXBfY2FuY2VyLCBhZGRfY2FuY2VyID0gTlVMTCwgaGV4Y29kZSwgdHlwZSA9ICJ6Iiwgc29ydCA9ICJkaWZmIiwgc2NhbGluZyA9ICJnZW5lLXdpc2UiLCByZXBvcnRfZGlyKSB7CiAgCiAgaWYgKCBjb21wX2NhbmNlciAhPSBpbnRfY2FuY2VyICkgewogICAgdGFyZ2V0cyA8LSB0YXJnZXRzWyB0YXJnZXRzJFRhcmdldCAlIWluJSBpbnRfY2FuY2VyLCBdCiAgICBkYXRhIDwtIGRhdGFbICxyb3duYW1lcyh0YXJnZXRzKSBdCiAgfQogIAogICMjIyMjIFBlcmZvcm0gWi1zY29yZSB0cmFuc2Zvcm1hdGlvbiBvZiB0aGUgbWVkaWFuIGV4cHJlc3Npb24gdmFsdWVzCiAgaWYgKCBzY2FsaW5nID09ICJnZW5lLXdpc2UiICkgewogICAgCiAgICBkYXRhLnogPC0gdChzY2FsZSh0KGRhdGEpKSkKICAgIHlfdGl0bGUgPC0gIm1STkEgZXhwcmVzc2lvbiAoWi1zY29yZSkiCiAgICAKICAgIGlmICggdHlwZSA9PSAicGVyYyIgKSB7CiAgICAgICMjIyMjIENvbnZlcnQgYSBleHByZXNzaW9uIHZhbHVlcyBpbnRvIGNvcnJlc3BvbmRpbmcgcGVyY2VudGlsZXMKICAgICAgZGF0YS56IDwtIHQoYXBwbHkoZGF0YS56LCAxLCBwZXJjLnJhbmspKQogICAgICB5X3RpdGxlIDwtICJtUk5BIGV4cHJlc3Npb24gKHBlcmNlbnRpbGUpIgogICAgfQogICAgCiAgfSBlbHNlIHsKICAgIGRhdGEueiA8LSBzY2FsZShkYXRhLCBzY2FsZSA9IEZBTFNFKQogICAgCiAgICBpZiAoIHR5cGUgPT0gInBlcmMiICkgewogICAgICAjIyMjIyBDb252ZXJ0IGEgZXhwcmVzc2lvbiB2YWx1ZXMgaW50byBjb3JyZXNwb25kaW5nIHBlcmNlbnRpbGVzCiAgICAgIGRhdGEueiA8LSB0KGFwcGx5KGRhdGEueiwgMSwgcGVyYy5yYW5rKSkKICAgIH0KICB9CiAgCiAgdGFyZ2V0cyRUYXJnZXRbIHRhcmdldHMkVGFyZ2V0PT1zYW1wbGVOYW1lIF0gPC0gIlBhdGllbnQiCiAgCiAgIyMjIyMgTWFrZSBzdXJlIHRoYXQgYWxsIGdlbmVzIGFyZSBwcmVzZW50IGluIHRoZSBleHByZXNzaW9uIG1hdHJpeAogIGdlbmVzIDwtIGdlbmVzWyBnZW5lcyAlaW4lIHJvd25hbWVzKGRhdGEueikgXQogIAogICMjIyMjIEdlbmVzIHNvcnRpbmcgZm9yIHZpc3VhbGlzYXRpb24KICAjIyMjIyBTb3J0IGdlbmVzIGJ5IHRoZSBncmVhdGVzdCBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHBhdGllbnQgYW5kIHRoZSAiY29tcF9jYW5jZXIiIGNvaG9ydAogIGlmICggc29ydCA9PSAiZGlmZiIgKSB7CiAgICBjb21wX2NhbmNlci5tZWRpYW5zIDwtIHJvd01lZGlhbnMoIGRhdGEuelsgZ2VuZXMgLHRhcmdldHMkVGFyZ2V0PT1jb21wX2NhbmNlciBdICkKICAgIG5hbWVzKGNvbXBfY2FuY2VyLm1lZGlhbnMpIDwtIGdlbmVzCiAgICBjb21wX2NhbmNlci5tZWRpYW5zLmRpZmYgPC0gY29tcF9jYW5jZXIubWVkaWFucyAtIGRhdGEuelsgZ2VuZXMgLHRhcmdldHMkVGFyZ2V0PT0iUGF0aWVudCIgXQogICAgZ2VuZXMgPC0gZ2VuZXNbIG9yZGVyKGNvbXBfY2FuY2VyLm1lZGlhbnMuZGlmZikgXQogIAogICMjIyMjIFNvcnQgZ2VuZXMgYWxwaGFiZXRpY2FsbHkKICB9IGVsc2UgaWYgKHNvcnQgPT0gImFscGhhYmV0aWNhbGx5IikgewogICAgZ2VuZXMgPC0gZ2VuZXNbIG9yZGVyKGdlbmVzKSBdCiAgfQoKICAjIyMjIyBQcmVwYXJlIGRhdGFmcmFtZSBmb3IgcGxvdGx5CiAgZ2VuZS5leHByLmRmIDwtIE5VTEwKICAKICBmb3IgKCBnZW5lIGluIGdlbmVzICkgewogICAgZ2VuZS5leHByLmRmIDwtIHJiaW5kKGdlbmUuZXhwci5kZiwgZGF0YS5mcmFtZShnZW5lLCB0YXJnZXRzJFRhcmdldCwgZGF0YS56W2dlbmUsIF0pKQogIH0KICBjb2xuYW1lcyhnZW5lLmV4cHIuZGYpIDwtIGMoIkdlbmUiLCAiR3JvdXAiLCAiRXhwcmVzc2lvbiIpCiAgCiAgIyMjIyMgUmVvcmRlciBncm91cHMKICBpZiAoICFpcy5udWxsKGFkZF9jYW5jZXIpICkgewogICAgZ2VuZS5leHByLmRmJEdyb3VwIDwtIGZhY3RvcihnZW5lLmV4cHIuZGYkR3JvdXAsIGxldmVscz1jKCJQYXRpZW50IiwgaW50X2NhbmNlciwgZXh0X2NhbmNlciwgYWRkX2NhbmNlcikpCiAgICBncm91cC5jb2xvdXJzIDwtIGMoSSgiYmxhY2siKSwgInJlZCIsICJjb3JuZmxvd2VyYmx1ZSIsICJmb3Jlc3RncmVlbiIpCiAgICAKICB9IGVsc2UgewogICAgZ2VuZS5leHByLmRmJEdyb3VwIDwtIGZhY3RvcihnZW5lLmV4cHIuZGYkR3JvdXAsIGxldmVscz1jKCJQYXRpZW50IiwgaW50X2NhbmNlciwgZXh0X2NhbmNlcikpCiAgICBncm91cC5jb2xvdXJzIDwtIGMoSSgiYmxhY2siKSwgInJlZCIsICJjb3JuZmxvd2VyYmx1ZSIpCiAgfQogIAogIHAgPC0gcGxvdF9seSggZ2VuZS5leHByLmRmLCB4ID0gfkdlbmUsIHkgPSB+RXhwcmVzc2lvbiwgY29sb3IgPSB+R3JvdXAsIHR5cGUgPSAiYm94IiwgY29sb3JzID0gZ3JvdXAuY29sb3Vycywgb3BhY2l0eT0wLjMsIHNob3dsZWdlbmQgPSBUUlVFLCB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gNDAwICkgJT4lIAogICAgYWRkX21hcmtlcnMoeCA9IH5HZW5lWyBnZW5lLmV4cHIuZGYkR3JvdXAgJWluJSAiUGF0aWVudCIgXSwgeSA9IH5FeHByZXNzaW9uWyBnZW5lLmV4cHIuZGYkR3JvdXAgJWluJSAiUGF0aWVudCIgXSwgY29sb3IgPSB+R3JvdXBbIGdlbmUuZXhwci5kZiRHcm91cCAlaW4lICJQYXRpZW50IiBdLCBtYXJrZXIgPSBsaXN0KHNpemUgPSA3KSwgb3BhY2l0eT0xLCBzaG93bGVnZW5kID0gRkFMU0UpICU+JQogICAgCiAgICBsYXlvdXQoYm94bW9kZSA9ICJncm91cCIsIHhheGlzID0gbGlzdCh0aXRsZSA9ICIiKSwgeWF4aXMgPSBsaXN0KHRpdGxlID0geV90aXRsZSksIGxlZ2VuZCA9IGxpc3QoIG9yaWVudGF0aW9uID0gJ2gnLCB5ID0gbWF4KGdlbmUuZXhwci5kZiRFeHByZXNzaW9uKSwgeWFuY2hvID0gInRvcCIsIGJnY29sb3IgPSAid2hpdGUiKSkKICAgIAogICMjIyMjIENyZWF0ZSBkaXJlY3RvcnkgZm9yICJhdCBnbGFuY2UiIHBsb3RzCiAgUGxvdHNEaXIgPC0gcGFzdGUocmVwb3J0X2RpciwgImdsYW5jZUV4cHJQbG90cyIsIHNlcCA9ICIvIikKICAgIAogIGlmICggIWZpbGUuZXhpc3RzKFBsb3RzRGlyKSApIHsKICAgIGRpci5jcmVhdGUoUGxvdHNEaXIsIHJlY3Vyc2l2ZT1UUlVFKQogIH0KICAKICAjIyMjIyBTYXZlIGludGVyYWN0aXZlIHBsb3QgYXMgaHRtbCBmaWxlCiAgc2F2ZVdpZGdldEZpeChwLCBmaWxlID0gcGFzdGUoUGxvdHNEaXIsIHBhc3RlMChoZXhjb2RlLCAiX2dsYW5jZV9leHByX3Bsb3QuIiwgdHlwZSwgIi5odG1sIiksIHNlcCA9ICIvIikpCiAgCiAgcmV0dXJuKCBwICkKCiAgIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CiAgcm0odGFyZ2V0cywgZGF0YSwgc2FtcGxlTmFtZSwgZGF0YS56LCB5X3RpdGxlLCBnZW5lcywgY29tcF9jYW5jZXIubWVkaWFucywgY29tcF9jYW5jZXIubWVkaWFucy5kaWZmLCBnZW5lLmV4cHIuZGYsIGdyb3VwLmNvbG91cnMpCiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCn0KCiMjIyMjIEdlbmVyYXRlIHNjYXR0ZXJwbG90IHdpdGggcGVyLWdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgKHktYXhpcyksIENOIHZhbHVlcyAoeC1heGlzKSBhbmQgbXV0YXRpb24gc3RhdHVzIGluZm8gKGNvbG91cnMpLCBpZiBwcm92aWRlZAptdXRDTmV4cHJQbG90IDwtIGZ1bmN0aW9uKGRhdGEsIGFsdF9kYXRhID0gRkFMU0UsIGNuX2JvdHRvbSA9IGNuX2JvdHRvbSwgY25fdG9wID0gY25fdG9wLCBjb21wX2NhbmNlciwgdHlwZSA9ICJ6IiwgcmVwb3J0X2RpcikgewogIAogICMjIyMjIEV4dHJhY3QgaW5mbyBmb3IgZ2VuZXMgdG8gYmUgYW5ub3RhdGVkIG9uIHRoZSBwbG90CiAgZ2VuZXMyYW5ub3QgPC0gZGF0YVsgZGF0YSRDTiA+PSBjbl90b3AgfCBkYXRhJENOIDw9IGNuX2JvdHRvbSAsXSRHZW5lCiAgCiAgaWYgKCBsZW5ndGgoZ2VuZXMyYW5ub3QpID09IDAgKSB7CiAgICBnZW5lczJhbm5vdCA8LSAiIgogIH0KICAKICBpZiAoIHR5cGUgPT0gInoiICkgewogICAgbmFtZXMoZGF0YSlbIG5hbWVzKGRhdGEpICVpbiUgIlpfc2NvcmVfZGlmZiIgXSA8LSAiRXhwciIKICAgIHlfdGl0bGUgPC0gcGFzdGUwKCJtUk5BIGV4cHJlc3Npb24gKFotc2NvcmUgW1BhdGllbnQgdnMgIiwgY29tcF9jYW5jZXIsICJdKSIpCiAgICAgIAogIH0gZWxzZSBpZiAoIHR5cGUgPT0gInBlcmMiICkgewogICAgbmFtZXMoZGF0YSlbIG5hbWVzKGRhdGEpICVpbiUgIlBlcmNfZGlmZiIgXSA8LSAiRXhwciIKICAgIHlfdGl0bGUgPC0gcGFzdGUwKCJtUk5BIGV4cHJlc3Npb24gKHBlcmNlbnRpbGUgW1BhdGllbnQgdnMgIiwgY29tcF9jYW5jZXIsICJdKSIpCiAgfQogIAogICMjIyMjIEdlbmVyYXRlIHNjYXR0ZXJwbG90IHdpdGggcGVyLWdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgKHktYXhpcykgKGRpZmZlcmVuY2UgYmV0d2VlbiBQYXRpZW50J3MgYW5kIFtjb21wX2NhbmNlcl0gZGF0YSksIENOIHZhbHVlcyAoeC1heGlzKSBhbmQgbXV0YXRpb24gc3RhdHVzIGluZm8gKGNvbG91cnMpCiAgaWYgKCBhbHRfZGF0YSApIHsKICAgIHAgPC0gcGxvdF9seSh0eXBlPSdzY2F0dGVyJywgbW9kZSA9ICJtYXJrZXJzIiwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDYwMCwgc2hvd2xlZ2VuZCA9IEZBTFNFKSAlPiUKICAgICAgCiAgICAgIGFkZF9tYXJrZXJzKGRhdGEgPSBkYXRhLCB5ID0gfkV4cHIsIHggPSB+Q04sIAogICAgICAgICAgICAgICAgbmFtZSA9IH5HZW5lLAogICAgICAgICAgICAgICAgdGV4dCA9IHBhc3RlMCgiR2VuZTogIiwgZGF0YSRHZW5lLCAgIlxuQWx0ZXJhdGlvbnM6ICIsIGRhdGEkQWx0ZXJhdGlvbnMpLAogICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywKICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZT0xMCwgc3ltYm9sPSJjaXJjbGUiKSwKICAgICAgICAgICAgICAgIGNvbG9yID0gfkdlbmUsCiAgICAgICAgICAgICAgICBzaG93bGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgIGxlZ2VuZHRpdGxlPVRSVUUsIAogICAgICAgICAgICAgICAgaW5oZXJpdCA9IEZBTFNFKSAlPiUKICAgICAgCiAgICAgIGFkZF9hbm5vdGF0aW9ucyggZGF0YSA9IGRhdGFbIGRhdGEkQ04gPj0gY25fdG9wIHwgZGF0YSRDTiA8PSBjbl9ib3R0b20gLF0sIHRleHQ9Z2VuZXMyYW5ub3QsCiAgICAgICAgICAgICAgICAgICAgICB4PX5DTiwgeGFuY2hvcj0ibGVmdCIsCiAgICAgICAgICAgICAgICAgICAgICB5PX5FeHByLCB5YW5jaG9yPSJ0b3AiLAogICAgICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3QoY29sb3IgPSAiR3JleSIsIHNpemUgPSAxMCksCiAgICAgICAgICAgICAgICAgICAgICBsZWdlbmR0aXRsZT1UUlVFLCBzaG93YXJyb3c9RkFMU0UgKSAlPiUKICAgICAgCiAgICAgIGxheW91dCggeGF4aXMgPSBsaXN0KHRpdGxlID0gIkNOIHZhbHVlIiksIHlheGlzID0gbGlzdCh0aXRsZSA9IHlfdGl0bGUpLCBtYXJnaW4gPSBsaXN0KGw9NTAsIHI9NTAsIGI9NTAsIHQ9NTAsIHBhZD00KSwgYXV0b3NpemUgPSBGLCBsZWdlbmQgPSBsaXN0KCBvcmllbnRhdGlvbiA9ICd2JywgeD0xLCB5PTAuOTcsIHlhbmNob3I9InRvcCIpLCBzaG93bGVnZW5kPVRSVUUpCiAgCiAgIyMjIyMgR2VuZXJhdGUgc2NhdHRlcnBsb3Qgd2l0aCBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyAoeS1heGlzKSBhbmQgQ04gdmFsdWVzICh4LWF4aXMpCiAgfSBlbHNlIHsKICAgIHAgPC0gcGxvdF9seShkYXRhLCB4ID0gfkNOLCB5ID0gfkV4cHIsIHRleHQ9fkdlbmUsIGNvbG9yID0gfkdlbmUsIHR5cGU9J3NjYXR0ZXInLCBtb2RlID0gIm1hcmtlcnMiLCBtYXJrZXIgPSBsaXN0KHNpemU9MTAsIHN5bWJvbD0iY2lyY2xlIiksIHdpZHRoID0gODAwLCBoZWlnaHQgPSA2MDApICU+JQogICAgICAKICAgICAgYWRkX2Fubm90YXRpb25zKCBkYXRhID0gZGF0YVsgZGF0YSRDTiA+PSBjbl90b3AgfCBkYXRhJENOIDw9IGNuX2JvdHRvbSAsXSwgdGV4dD1+R2VuZSwKICAgICAgICAgICAgICAgICAgICAgIHg9fkNOLCB4YW5jaG9yPSJsZWZ0IiwKICAgICAgICAgICAgICAgICAgICAgIHk9fkV4cHIsIHlhbmNob3I9InRvcCIsCiAgICAgICAgICAgICAgICAgICAgICBmb250ID0gbGlzdChjb2xvciA9ICJHcmV5IiwKICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCksCiAgICAgICAgICAgICAgICAgICAgICBsZWdlbmR0aXRsZT1UUlVFLCBzaG93YXJyb3c9RkFMU0UgKSAlPiUKICAgICAgCiAgICAgIGxheW91dCggeGF4aXMgPSBsaXN0KHRpdGxlID0gIkNOIHZhbHVlIiksIHlheGlzID0gbGlzdCh0aXRsZSA9ICB5X3RpdGxlKSwgbWFyZ2luID0gbGlzdChsPTUwLCByPTUwLCBiPTUwLCB0PTUwLCBwYWQ9NCksIGF1dG9zaXplID0gRiwgbGVnZW5kID0gbGlzdCggb3JpZW50YXRpb24gPSAndicsIHk9MC44LCB5YW5jaG9yPSJ0b3AiKSwgc2hvd2xlZ2VuZD1UUlVFKQogIH0KICAKICAjIyMjIyBDcmVhdGUgZGlyZWN0b3J5IGZvciB0aGUgcGxvdHMKICBtdXRDTmV4cHJQbG90RGlyIDwtIHBhc3RlKHJlcG9ydF9kaXIsICJjbl9leHByX3Bsb3QiLCBzZXAgPSAiLyIpCiAgaWYgKCAhZmlsZS5leGlzdHMobXV0Q05leHByUGxvdERpcikgKSB7CiAgICBkaXIuY3JlYXRlKG11dENOZXhwclBsb3REaXIsIHJlY3Vyc2l2ZT1UUlVFKQogIH0KICAKICAjIyMjIyBTYXZlIGludGVyYWN0aXZlIHBsb3QgYXMgaHRtbCBmaWxlCiAgc2F2ZVdpZGdldEZpeChwLCBmaWxlID0gcGFzdGUobXV0Q05leHByUGxvdERpciwgcGFzdGUwKCJjbl9leHByX3Bsb3QuIiwgdHlwZSwgIi5odG1sIiksIHNlcCA9ICIvIikpCiAgICAKICByZXR1cm4oIHAgKQogIAogICMjIyMjIENsZWFuIHRoZSBzcGFjZSBhbmQgcmV0dXJuIG91dHB1dAogIHJtKGRhdGEsIGFsdF9kYXRhLCBnZW5lczJhbm5vdCwgeV90aXRsZSkKICAKICAjIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKfQoKIyMjIyMgRnVzaW9uIHZpc3VhbGlzYXRpb24gCmFycmliYV9wbG90cyA8LSBmdW5jdGlvbihhcnJpYmFfZmlsZSwgYXJyaWJhX3Jlc3VsdHMsIHJlc3VsdHNfZGlyKSB7CgogICMjIyMjIEdldCBwYXRoIHRvIGZ1c2lvbiB2aXN1YWxpc2F0aW9uICBwZGYgZmlsZQogIGFycmliYV9kaXIgPC0gdW5saXN0KHN0cnNwbGl0KGFycmliYV9maWxlLCBzcGxpdD0nLycsIGZpeGVkPVRSVUUpKQogIGFycmliYV9wbG90cy5wZGYgPC0gbGlzdC5maWxlcyhwYXN0ZShhcnJpYmFfZGlyWzE6bGVuZ3RoKGFycmliYV9kaXIpLTFdLCBjb2xsYXBzZSA9ICIvIiksIHBhdHRlcm49IlxcLnBkZiQiKQogIGFycmliYV9kaXIgPC0gcGFzdGUoYXJyaWJhX2RpclsxOmxlbmd0aChhcnJpYmFfZGlyKS0xXSwgY29sbGFwc2UgPSAiLyIpCiAgYXJyaWJhX3Bsb3RzLnBkZiA8LSBwYXN0ZShhcnJpYmFfZGlyLCBhcnJpYmFfcGxvdHMucGRmLCBzZXAgPSAiLyIpCiAgICAKICAjIyMjIyBDcmVhdGUgZGlyZWN0b3J5IGZvciByZXN1bHRzCiAgaWYgKCAhZmlsZS5leGlzdHMocmVzdWx0c19kaXIpICkgewogICAgZGlyLmNyZWF0ZShyZXN1bHRzX2RpciwgcmVjdXJzaXZlPVRSVUUpCiAgfQogIAogICMjIyMjIEV4cG9ydCBwZGYgaW1hZ2VzIHRvIHBuZwogIGZvciAoIGkgaW4gMTpucm93KGFycmliYV9yZXN1bHRzKSApIHsKICAgIGFycmliYV9wbG90cy5wbmcgPC0gZ3N1YigiOiIsICIuIiwgcGFzdGUwKHJlc3VsdHNfZGlyLCAiLyIsIG1ha2UubmFtZXMocGFzdGUoYXJyaWJhX3Jlc3VsdHMkWC5nZW5lMVtpXSwgYXJyaWJhX3Jlc3VsdHMkZ2VuZTJbaV0sIHNlcCA9ICJfXyIpKSwgIl8iLCBhcnJpYmFfcmVzdWx0cyRicmVha3BvaW50MVtpXSwgIi0iLCBhcnJpYmFfcmVzdWx0cyRicmVha3BvaW50MltpXSwgIi5wbmciKSkKICAgIGZ1c2lvbiA8LSBwZGZfcmVuZGVyX3BhZ2UoYXJyaWJhX3Bsb3RzLnBkZiwgcGFnZSA9IGksIGRwaSA9IDMwMCwgbnVtZXJpYyA9IFRSVUUsIG9wdyA9ICIiLCB1cHcgPSAiIikKICAgIHdyaXRlUE5HKGZ1c2lvbiwgYXJyaWJhX3Bsb3RzLnBuZykKICB9CgogICMjIyMjIENsZWFuIHRoZSBzcGFjZQogIHJtKGFycmliYV9wbG90cy5wZGYsIGFycmliYV9wbG90cy5wbmcsIGZ1c2lvbikKICAKICAjIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKfQoKIyMjIyMgR2VuZXJhdGUgdGFibGUgd2l0aCBjb2xvdXJlZCBjZWxscyBpbmRpY2F0aW5nIGV4cHJlc3Npb24gdmFsdWVzIGZvciBzZWxlY3RlZCBnZW5lcwpleHByVGFibGUgPC0gZnVuY3Rpb24oZ2VuZXMsIGtlZXBfYWxsID0gRkFMU0UsIGRhdGEsIGNuX2RhdGEgPSBOVUxMLCBzdl9kYXRhID0gTlVMTCwgY25fZGVjcmVhc2UgPSBUUlVFLCB0YXJnZXRzLCBzYW1wbGVOYW1lLCBpbnRfY2FuY2VyLCBleHRfY2FuY2VyLCBjb21wX2NhbmNlciwgYWRkX2NhbmNlciA9IE5VTEwsIGdlbmVzX2Fubm90ID0gTlVMTCwgb25jb2tiX2Fubm90ID0gTlVMTCwgY2FuY2VyX2dlbmVzID0gTlVMTCwgbXV0X2Fubm90ID0gTlVMTCwgZnVzaW9uX2dlbmVzID0gTlVMTCwgZXh0X2xpbmtzID0gRkFMU0UsIHR5cGUgPSAieiIsIHNjYWxpbmcgPSAiZ2VuZS13aXNlIikgewogIAogICMjIyMjIENoZWNrIHdoaWNoIG9mIHRoZSBzZWxlY3RlZCBnZW5lcyBhcmUgbm90IHByZXNlbnQgaW4gdGhlIGV4cHJlc3Npb24gZGF0YQogIGdlbmVzLmFic2VudCA8LSBnZW5lc1sgZ2VuZXMgJSFpbiUgcm93bmFtZXMoZGF0YSkgXQogICAgCiAgIyMjIyMgSW5pdGlhdGUgZGF0YWZyYW1lIGZvciBleHByZXNzaW9uIG1lZGlhbiB2YWx1ZXMgaW4gZWFjaCBncm91cAogIHRhcmdldHMubGlzdCA8LSB1bmlxdWUodGFyZ2V0cyRUYXJnZXQpCiAgZ3JvdXAueiA8LSBhcy5kYXRhLmZyYW1lKG1hdHJpeChOQSwgbmNvbCA9IGxlbmd0aCh0YXJnZXRzLmxpc3QpLCBucm93ID0gbnJvdyhkYXRhKSkpCiAgY29sbmFtZXMoZ3JvdXAueikgPC0gdGFyZ2V0cy5saXN0CiAgcm93bmFtZXMoZ3JvdXAueikgPC0gcm93bmFtZXMoZGF0YSkKICAgIAogICMjIyMjIFBlcmZvcm0gc2NhbGluZyBnZW5lLXdpc2UKICBpZiAoIHNjYWxpbmcgPT0gImdlbmUtd2lzZSIgKSB7CiAgICAjIyMjIyBDYWxjdWxhdGUgei1zY29yZSBmb3IgZWFjaCBncm91cCAgCiAgICBncm91cC5zdGF0cyA8LSBleHByR3JvdXBzU3RhdHNfZ2VuZVdpc2UoZGF0YSwgdGFyZ2V0cylbWzFdXQogICAgCiAgICAjIyMjIyBNYWtlIHN1cmUgdG8gaW5jbHVkZSBvbmx5IGdlbmVzIGZvciB3aGljaCBaLXNjb3JlcyB3ZXJlIGNhbGFjdWxhdGVkICAoZ2VuZXMgd2l0aCBTRCA9IDAgYWNyb3NzIGFsbCBzYW1wbGVzIHdpbGwgZ2l2ZSBOQSkKICAgIGdyb3VwLnogPC0gZ3JvdXAuelsgcm93bmFtZXMoZ3JvdXAueikgJWluJSByb3duYW1lcyhncm91cC5zdGF0c1tbdGFyZ2V0cy5saXN0WzFdXV0pLCBdCiAgICAKICAgICMjIyMgUHJlc2VudCBleHByZXNzaW9uIGRhdGEgYXMgcGVyY2VudGlsZXMgb3Igei1zY29yZSB2YWx1ZXMgKGRlZmF1bHQpCiAgICBmb3IgKCBncm91cCBpbiB0YXJnZXRzLmxpc3QgKSB7CiAgICAgIGlmICggdHlwZSA9PSAicGVyYyIgKSB7CiAgICAgICAgZ3JvdXAuelssIGdyb3VwXSA8LSByb3VuZChncm91cC5zdGF0c1tbIGdyb3VwIF1dJHF1YW50aWxlLCBkaWdpdHM9MSkKICAgICAgfSBlbHNlIHsKICAgICAgICBncm91cC56WywgZ3JvdXBdIDwtIHJvdW5kKGdyb3VwLnN0YXRzW1sgZ3JvdXAgXV0keiwgZGlnaXRzPTIpCiAgICAgIH0KICAgIH0KICAgIAogICMjIyMjIFBlcmZvcm0gc2NhbGluZyBncm91cC13aXNlCiAgfSBlbHNlIHsKICAgIGZvciAoIGdyb3VwIGluIHRhcmdldHMubGlzdCApIHsKICAgICAgCiAgICAgICMjIyMjIENhbGN1bGF0ZSB6LXNjb3JlIGZvciBlYWNoIGdyb3VwICAKICAgICAgZ3JvdXAuc3RhdHMgPC0gZXhwckdyb3VwU3RhdHNfZ3JvdXBXaXNlKGRhdGFbcm93bmFtZXMoZ3JvdXAueiksIF0sIHRhcmdldHMsIGdyb3VwKQogICAgICBncm91cC5zdGF0cyA8LSBncm91cC5zdGF0c1tvcmRlcihyb3duYW1lcyhncm91cC5zdGF0cykpLCBdCiAgICAgIAogICAgICAjIyMjIFByZXNlbnQgZXhwcmVzc2lvbiBkYXRhIGFzIHBlcmNlbnRpbGVzIG9yIHotc2NvcmUgdmFsdWVzIChkZWZhdWx0KQogICAgICBpZiAoIHR5cGUgPT0gInBlcmMiICkgewogICAgICAgIGdyb3VwLnpbLCBncm91cF0gPC0gcm91bmQoZ3JvdXAuc3RhdHMkcXVhbnRpbGUsIGRpZ2l0cz0xKQogICAgICB9IGVsc2UgewogICAgICAgIGdyb3VwLnpbLCBncm91cF0gPC0gcm91bmQoZ3JvdXAuc3RhdHMkeiwgZGlnaXRzPTIpCiAgICAgIH0KICAgIH0gCiAgfQogIAogICMjIyMjIElmIGFkZGl0aW9uYWwgY2FuY2VyIHR5cGUgaXMgZGVmaW5lZCB0aGVuIHJlbW92ZSBpdCBmcm9tIHRoZSBkYXRhCiAgaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyKSApIHsKICAgIGdyb3VwLnogPC0gZ3JvdXAuelsgLCBuYW1lcyhncm91cC56KSAlIWluJSBhZGRfY2FuY2VyIF0KICAgIHRhcmdldHMgPC0gdGFyZ2V0c1sgdGFyZ2V0cyRUYXJnZXQgJSFpbiUgYWRkX2NhbmNlciwgXQogICAgdGFyZ2V0cy5saXN0IDwtIHRhcmdldHMubGlzdFsgdGFyZ2V0cy5saXN0ICUhaW4lIGFkZF9jYW5jZXIgXQogIH0KICAKICAjIyMjIyBDb21wdXRlIFotc2NvcmVzIHNkIGZvciBlYWNoIGdlbmUgYWNyb3NzIGdyb3VwcwogIGdyb3VwLnogPC0gY2JpbmQoZ3JvdXAueiwgcm91bmQocm93U2RzKGFzLm1hdHJpeChncm91cC56KSksIGRpZ2l0cyA9IDIpKQogIG5hbWVzKGdyb3VwLnopW25jb2woZ3JvdXAueildIDwtICJTRCIKICAKICAjIyMjIyBDYWxjdWxhdGUgWi1zY29yZSBkaWZmZXJuZWNlcyBiZXR3ZWVuIGludmVzdGlnYXRlZCBzYW1wbGUgYW5kIG1lZGlhbiB2YWx1ZXMgaW4gdGhlIGNhbmNlciBncm91cCBvZiBpbnRlcmVzdAogIGdyb3VwLnogPC0gY2JpbmQoZ3JvdXAueiwgcm91bmQoKGdyb3VwLnpbLCBzYW1wbGVOYW1lXSAtIGdyb3VwLnpbLCBjb21wX2NhbmNlcl0pLCBkaWdpdHMgPSAyKSkKICBuYW1lcyhncm91cC56KVtuY29sKGdyb3VwLnopXSA8LSAiRGlmZiIKICAKICAjIyMjIyBBZGQgTkFzIGZvciBnZW5lcyB0aGF0IGFyZSBhYnNlbnQgaW4gdGhlIGV4cHJlc3Npb24gbWF0cml4LiBJbiB0aGUgIlBhdGllbnQgdnMgW2NvbXBfY2FuY2VyXSIgY29sdW1ucyBwcm92aWRlICIwInMgdG8gZmFjaWxpdGF0ZSBpbnRlcmFjdGl2ZSBzb3J0aW5nIHRoZSB0YWJsZS4gVGhlc2Ugd2lsbCBhcHBlYXIgaW4gYmxhbmsgY2VsbHMgaW4gdGhlIHRhYmxlCiAgaWYgKCBsZW5ndGgoZ2VuZXMuYWJzZW50KSA+IDAgKSB7CiAgICAKICAgIE5Bcy5kZiA8LSBkYXRhLmZyYW1lKG1hdHJpeChOQSwgbmNvbCA9IG5jb2woZ3JvdXAueiksIG5yb3cgPSBsZW5ndGgoZ2VuZXMuYWJzZW50KSkpCiAgICBuYW1lcyhOQXMuZGYpIDwtIG5hbWVzKGdyb3VwLnopCiAgICByb3duYW1lcyhOQXMuZGYpIDwtIGdlbmVzLmFic2VudAogICAgTkFzLmRmWyBuYW1lcyhOQXMuZGYpICVpbiUgIkRpZmYiIF0gPC0gMAogICAgZ3JvdXAueiA8LSByYmluZCggZ3JvdXAueiwgIE5Bcy5kZikKICB9CiAgCiAgIyMjIyMgQ2hhbmdlIHNhbXBsZSBJRCB0byAiUGF0aWVudCIgZm9yIGJldHRlciB2aXN1YWxpc2F0aW9uCiAgbmFtZXMoZ3JvdXAueilbbmFtZXMoZ3JvdXAueik9PXNhbXBsZU5hbWVdIDwtICJQYXRpZW50IgogIHRhcmdldHMubGlzdFt0YXJnZXRzLmxpc3Q9PXNhbXBsZU5hbWVdIDwtICJQYXRpZW50IgogIAogICMjIyMjIFJlb3JkZXIgZ3JvdXBzCiAgZ3JvdXAueiA8LSBjYmluZChncm91cC56WyAsIGMoZXh0X2NhbmNlciwgaW50X2NhbmNlciwgIlBhdGllbnQiKV0sIGdyb3VwLnpbLCBjKCJTRCIsICJEaWZmIiApXSkKICAKICAjIyMjIyBBZGQgIkdlbmUiIGNvbHVtbiB0byBmYWNpbGl0YXRlIGFkZGluZyBhbm5vdGF0aW9ucwogIGdyb3VwLnokR2VuZSA8LSByb3duYW1lcyhncm91cC56KQogIAogICMjIyMjIEFkZCBnZW5lcyBhbm5vdGF0aW9uCiAgaWYgKCAhaXMubnVsbChnZW5lc19hbm5vdCkgKSB7CiAgICAjIyMjIyBSZW1vdmUgcm93cyB3aXRoIGR1cGxpY2F0ZWQgZ2VuZSBzeW1ib2xzCiAgICBpZiAoICJTWU1CT0wiICVpbiUgbmFtZXMoZ2VuZXNfYW5ub3QpICkgewogICAgICBnZW5lc19hbm5vdCA8LSBnZW5lc19hbm5vdFshZHVwbGljYXRlZChnZW5lc19hbm5vdCRTWU1CT0wpLF0gIAogICAgfQogICAgCiAgICAjIyMjIyBNZXJnZSB0aGUgZGF0YWZyYW1lIHdpdGggZ3JvdXBzIG1lZGlhbiBleHByZXNzaW9uIHZhbHVlcyBhbmQgZ2VuZSBhbm5vdGF0aW9ucwogICAgZ3JvdXAueiA8LSBtZXJnZShnZW5lc19hbm5vdCwgZ3JvdXAueiwgYnkueD0iU1lNQk9MIiwgYnkueT0iR2VuZSIsIGFsbCA9IFRSVUUsIHNvcnQgPSBGQUxTRSkKICAgIG5hbWVzKGdyb3VwLnopIDwtIGdzdWIoIlNZTUJPTCIsICJHZW5lIiwgbmFtZXMoZ3JvdXAueikpCiAgfQogIAogICMjIyMjIERlZmluZSBjb2xvdXJzIGZvciBjZWxscyBiYWNrZ3JvdW5kIGZvciBlYWNoIGdyb3VwIGFuZCB0aGUgcGF0aWVudCB2cyBbY29tcF9jYW5jZXJdIGRpZmZlcmVuY2UKICAjIyMjIyBJbml0aWF0ZSBkYXRhZnJhbWUgZm9yIGV4cHJlc3Npb24gbWVkaWFuIHZhbHVlcyBpbiBlYWNoIGdyb3VwCiAgYnJrcy5xIDwtIGFzLmRhdGEuZnJhbWUoIG1hdHJpeChOQSwgbmNvbCA9IGxlbmd0aCh0YXJnZXRzLmxpc3QpLCBucm93ID0gbGVuZ3RoKHNlcSguMDUsIC45NSwgLjAwMDUpKSApKQogIGNvbG5hbWVzKGJya3MucSkgPC0gdGFyZ2V0cy5saXN0CiAgY2xycy5xIDwtIGFzLmRhdGEuZnJhbWUoIG1hdHJpeChOQSwgbmNvbCA9IGxlbmd0aCh0YXJnZXRzLmxpc3QpLCBucm93ID0gbGVuZ3RoKHNlcSguMDUsIC45NSwgLjAwMDUpKSsxICkpCiAgY29sbmFtZXMoY2xycy5xKSA8LSB0YXJnZXRzLmxpc3QKICAKICBmb3IgKCBncm91cCBpbiBjKHRhcmdldHMubGlzdCwgIkRpZmYiKSApIHsKICAgIGJya3MucVtbZ3JvdXBdXSA8LSBxdWFudGlsZShncm91cC56WywgZ3JvdXBdLCBwcm9icyA9IHNlcSguMDUsIC45NSwgLjAwMDUpLCBuYS5ybSA9IFRSVUUpCiAgICAKICAgIGNscnNfcG9zLnEgPC0gcm91bmQoc2VxKDI1NSwgMTUwLCBsZW5ndGgub3V0ID0gbGVuZ3RoKGJya3MucVtbZ3JvdXBdXSkvMiArIDEuNSksIDApICU+JQogICAge3Bhc3RlMCgicmdiKDI1NSwiLCAuLCAiLCIsIC4sICIpIil9CiAgICBjbHJzX25lZy5xIDwtIHJldihyb3VuZChzZXEoMjU1LCAxNTAsIGxlbmd0aC5vdXQgPSBsZW5ndGgoYnJrcy5xW1tncm91cF1dKS8yIC0gMC41KSwgMCkpICU+JQogICAge3Bhc3RlMCgicmdiKCIsIC4sIiwiLCAuLCIsIiwgIjI1NSkiKX0KICAgIGNscnMucVtbZ3JvdXBdXSA8LSBjKGNscnNfbmVnLnEsIGNscnNfcG9zLnEpCiAgfQogIAogICMjIyMjIFN1YnNldCB0aGUgZXhwcmVzc2lvbiBkYXRhIHRvIGluY2x1ZGUgb25seSB0aGUgdXNlci1kZWZpbmVkIGdlbmVzCiAgZ3JvdXAueiA8LSBncm91cC56WyBncm91cC56JEdlbmUgJWluJSBnZW5lcywgXQogICAgCiAgIyMjIyBBZGQgdmFyaWFudHMgaW5mb3JtYXRpb24gdG8gdGhlIGV4cHJlc3Npb24gdGFibGUgLSBpZiBleGlzdHMuIE5vdGUsICJUSUVSIiBhbmQgIkNPTlNFUVVFTkNFIiBjb2x1bW5zIGFyZSByZXF1aXJlZAogIGlmKCAhaXMubnVsbChtdXRfYW5ub3QpICYmICJUSUVSIiAlaW4lIGNvbG5hbWVzKG11dF9hbm5vdCkgJiYgbGVuZ3RoKGdlbmVzKSA+IDAgKSB7CiAgICBtdXRfYW5ub3QgPC0gbXV0X2Fubm90W211dF9hbm5vdCRTWU1CT0wgJWluJSBnZW5lcyxdCiAgICAKICAgICMjIyMga2VlcCBvbmx5IHZhcmFpbnRzIHRoYXQgaGFzIHRoZSBsb3dlc3QgdGllciB2YWx1ZS4gTXVsdGlwbGUgdmFyYWludHMgZGV0ZWN0ZWQgaW4gc2FtZSBnZW5lIGJ1dCB3aXRoIGhpZ2hlciB0aWVyIHdpbGwgYmUgYWRkZWQgdG8gYWRkaXRpb25hbCBjb2x1bW4gIkNPTlNFUVVFTkNFX09USEVSIi4gQXBwbGllcyB0byB0aGUgb25lcyB0aGF0IG1heSBoYXZlIG11bHRpcGxlIG11dGF0aW9ucyBhbmQgaGVuY2UgdGllcnMKICAgICMjIyMjIEZpcnN0LCBjcmVhdGUgYSBsaXN0IG9mIGdlbmVzIHRvIHN0b3JlIG11bHRpcGxlIHZhcmlhbnRzCiAgICBtdXRfY29uc2VxdWVuY2UgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoKHVuaXF1ZShtdXRfYW5ub3QkU1lNQk9MKSkpCiAgICBtdXRfY29uc2VxdWVuY2UgIDwtIHNldE5hbWVzKG11dF9jb25zZXF1ZW5jZSwgIHVuaXF1ZShtdXRfYW5ub3QkU1lNQk9MKSApCiAgICAKICAgICMjIyMjIFJlY29yZCBhbGwgdmFyYWludHMgZGV0ZWN0ZWQgaW4gaW5kaXZpZHVhbCBnZW5lcwogICAgaWYgKCBucm93KG11dF9hbm5vdCkgPiAwICkgewogICAgICBmb3IgKCBpIGluIDE6bnJvdyhtdXRfYW5ub3QpICkgewogICAgICAgIG11dF9jb25zZXF1ZW5jZVtbIG11dF9hbm5vdCRTWU1CT0xbaV0gXV0gPC0gdW5pcXVlKGMoIG11dF9jb25zZXF1ZW5jZVtbIG11dF9hbm5vdCRTWU1CT0xbaV0gXV0sIG11dF9hbm5vdCRDT05TRVFVRU5DRVtpXSApKQogICAgICB9CiAgICAgIAogICAgICBtdXRfYW5ub3QkQ09OU0VRVUVOQ0VfT1RIRVIgPC0gIi0iCiAgICB9CiAgICAKICAgICMjIyMjIFJlbW92ZSB0aGUgZmlyc3QgZWxlbWVudHMgc2luY2UgdGhlc2UgdmFyaWFudCBjb25zZXF1ZW5jZXMgd2lsbCBiZSByZXBvcnRlZCBhcyB0aGUgImNhbm9uaWNhbCIgQ09OU0VRVUVOQ0UKICAgIG11dF9jb25zZXF1ZW5jZSA8LSBsYXBwbHkobXV0X2NvbnNlcXVlbmNlLCBmdW5jdGlvbih4KSB4Wy0xXSkKICAgIAogICAgIyMjIyMgT3JkZXIgdmFyaWFudCBlbnRpcmVzIGJhc2VkIG9uIHRpZXIgaW5mbywgdG8gbWFrZSBzdXJlIHRoYXQgdGhlIHZhcmFpbnRzIHdpdGggdGhlIGxvd2VzdCB0aWVyIGFyZSByZXBvcnRlZCBmaXJzdAogICAgbXV0X2Fubm90IDwtIG11dF9hbm5vdFsgb3JkZXIobXV0X2Fubm90JFRJRVIpLCBdCiAgICAKICAgICMjIyMjIFJlbW92ZSByb3dzIHdpdGggZHVwbGljYXRlZCBnZW5lIHN5bWJvbHMKICAgIG11dF9hbm5vdCA8LSBtdXRfYW5ub3RbIWR1cGxpY2F0ZWQobXV0X2Fubm90JFNZTUJPTCksXSAgCiAgICByb3duYW1lcyhtdXRfYW5ub3QpIDwtIG11dF9hbm5vdCRTWU1CT0wKICAgIAogICAgIyMjIyMgQWRkIG90aGVyIHByb3ZpZGVkIHZhcmlhbnRzIGNvbnNlcXVlbmNlcyBmb3IgaW5kaXZpZHVhbCBnZW5lcwogICAgZm9yICggZ2VuZSBpbiByb3duYW1lcyhtdXRfYW5ub3QpICkgewogICAgICBpZiAoIGxlbmd0aChtdXRfY29uc2VxdWVuY2VbWyBnZW5lIF1dKSA+IDAgKSB7CiAgICAgICAgbXV0X2Fubm90JENPTlNFUVVFTkNFX09USEVSWyBtYXRjaChnZW5lLCBtdXRfYW5ub3QkU1lNQk9MKSAgXSA8LSBtdXRfY29uc2VxdWVuY2VbWyBnZW5lIF1dCiAgICAgIH0KICAgIH0KICAgIAogICAgIyMjIyBtZXJnZSB0aGUgdmFyaWFudHMgaW5mb3JtYXRpb24gd2l0aCB0aGUgZGF0YWZyYW1lCiAgICBncm91cC56IDwtIG1lcmdlKGdyb3VwLnosIG11dF9hbm5vdCwgYnkueCA9ICJHZW5lIiwgYnkueSA9ICJTWU1CT0wiLCBhbGwgPSBUUlVFLCBzb3J0ID0gRkFMU0UpCiAgfQogIAogICMjIyMjIEFkZCBDTiBkYXRhIGlmIHByb3ZpZGVkCiAgaWYgKCAhaXMubnVsbChjbl9kYXRhKSApIHsKICAgICMjIyMjIEdldCB0aGUgcG9zaXRpb24gb2YgIkRpZmYiIGNvbHVtbgogICAgY29sX2lkeCA8LSBncmVwKCJEaWZmIiwgbmFtZXMoZ3JvdXAueiksIGZpeGVkID0gVFJVRSkKICAgIAogICAgIyMjIyMgTm93IHBsYWNlIHRoZSBDTiBkYXRhIGFmdGVyIHRoZSAiRGlmZiIgY29sdW1uCiAgICBpZiAoIGxlbmd0aChnZW5lcykgPiAwICkgewogICAgICBncm91cC56IDwtIGFkZF9jb2x1bW4oZ3JvdXAueiwgcm91bmQoY25fZGF0YVsgZ3JvdXAueiRHZW5lLCAiQ04iXSwgZGlnaXRzPTIpLCAuYWZ0ZXIgPSBjb2xfaWR4KQogICAgICBjb2xuYW1lcyhncm91cC56KVsgY29sX2lkeCsxIF0gPC0gIlBhdGllbnQgKENOKSIKICAgICAgY25fcmFuZ2UgPC0gYmFzZTo6cmFuZ2UoZ3JvdXAuelsgLCJQYXRpZW50IChDTikiIF0sIG5hLnJtID0gVFJVRSkKICAgICAgCiAgICB9IGVsc2UgewogICAgICBncm91cC56IDwtIGFkZF9jb2x1bW4oZ3JvdXAueiwgIiIsIC5hZnRlciA9IGNvbF9pZHgpCiAgICAgIGNvbG5hbWVzKGdyb3VwLnopWyBjb2xfaWR4KzEgXSA8LSAiUGF0aWVudCAoQ04pIgogICAgICBjbl9yYW5nZSA8LSAwCiAgICB9CiAgfQoKICAjIyMjIyBBZGQgc3RydWN0dXJhbCB2YXJpYW50cyByZXN1bHRzIGZyb20gTUFOVEEKICBpZiAoICFpcy5udWxsKHN2X2RhdGEpICYmIGxlbmd0aChnZW5lcykgPiAwICkgewogICAgIyMjIyMgTk9URTogd2hlbiBtZXJnaW5nIHBlci1nZW5lIGV4cHJzc2lvbiBkYXRhIHdpdGggU1YgZGF0YSBmcm9tIE1BTlRBIHRoZSAiZ2VuZSIgY29sdW1uIGlzIHVzZWQgc2luY2UgbXVsdGlwbGUgZW50aXJlcyBhcmUgcG9zc2libGUgZm9yIG9uZSBnZW5lIGluIE1BTlRBIG91dHB1dAogICAgZ3JvdXAueiA8LSBtZXJnZShncm91cC56LCBzdl9kYXRhLCBieS54PSJHZW5lIiwgYnkueT0iR2VuZSIsIGFsbCA9IFRSVUUsIHNvcnQgPSBGQUxTRSkKICB9CiAgCiAgIyMjIyMgQWRkIGluZm8gYWJvdXQga25vd24gZnVzaW9uIGdlbmVzCiAgaWYgKCAhaXMubnVsbChmdXNpb25fZ2VuZXMpICYmIGxlbmd0aChnZW5lcykgPiAwICkgewogICAgCiAgICBncm91cC56JEZ1c2lvbl9nZW5lIDwtIE5BCiAgICBncm91cC56JEZ1c2lvbl9nZW5lWyBncm91cC56JEdlbmUgJWluJSBmdXNpb25fZ2VuZXMgIF0gPC0gIlllcyIKICB9CiAgCiAgIyMjIyMgQWRkIGNhbmNlciBnZW5lIHJlc291cmNlcyBpbmZvCiAgaWYgKCAhaXMubnVsbChjYW5jZXJfZ2VuZXMpICYmIGxlbmd0aChnZW5lcykgPiAwICkgewogICAgZ3JvdXAueiA8LSBtZXJnZShncm91cC56LCBjYW5jZXJfZ2VuZXMsIGJ5Lng9IkdlbmUiLCBieS55PSJyb3cubmFtZXMiLCBhbGwgPSBUUlVFLCBzb3J0ID0gRkFMU0UpCiAgfQogIAogICMjIyMjIEluY2x1ZGUgb25seSBxdWVyaWVkIGdlbmVzCiAgZ3JvdXAueiA8LSBncm91cC56WyBncm91cC56JEdlbmUgJWluJSBnZW5lcywgXQogIGdyb3VwLnokU1lNQk9MIDwtIGdyb3VwLnokR2VuZQogIAogICMjIyMjIEFkZCBsaW5rcyB0byBleHRlcm5hbCBnZW5lIGFubm90YXRpb24gcmVzb3Vyc2VzCiAgaWYgKCBleHRfbGlua3MgJiYgbGVuZ3RoKGdlbmVzKSA+IDAgKSB7CiAgICAKICAgICMjIyMjIFBsYWNlIHRoZSBleHRlcm5hbCBsaW5rcyBhZnRlciB0aGUgIkRpZmYiIGNvbHVtbgogICAgIyMjIyMgR2V0IHRoZSBwb3NpdGlvbiBvZiAiRGlmZiIgY29sdW1uCiAgICBjb2xfaWR4IDwtIGdyZXAoIkRpZmYiLCBuYW1lcyhncm91cC56KSwgZml4ZWQgPSBUUlVFKQogICAgZ3JvdXAueiA8LSBhZGRfY29sdW1uKGdyb3VwLnosIE5BLCAuYWZ0ZXIgPSBjb2xfaWR4KQogICAgbmFtZXMoZ3JvdXAueilbIGNvbF9pZHgrMSBdIDwtICJleHRfbGlua3MiCiAgICAKICAgIGZvciAoIGdlbmUgaW4gZ2VuZXMgKSB7CiAgICAgICMjIyMjIFByb3ZpZGUgbGluayB0byBWSUNDIG1ldGEta25vd2xlZGdlYmFzZSAoIGh0dHBzOi8vc2VhcmNoLmNhbmNlcnZhcmlhbnRzLm9yZyApCiAgICAgIGdyb3VwLnokZXh0X2xpbmtzWyBncm91cC56JEdlbmU9PWdlbmUgXSA8LSBwYXN0ZTAoIjxhIGhyZWY9J2h0dHBzOi8vc2VhcmNoLmNhbmNlcnZhcmlhbnRzLm9yZy8jIiwgZ2VuZSwgIicgdGFyZ2V0PSdfYmxhbmsnPlZJQ0M8L2E+IikKICAgICAgCiAgICAgICMjIyMjIFByb3ZpZGUgbGluayB0byBPbmNvS0IKICAgICAgaWYgKCAhaXMubnVsbChvbmNva2JfYW5ub3QpICkgewogICAgICAgIGlmICggZ2VuZSAlaW4lIHJvd25hbWVzKG9uY29rYl9hbm5vdCkgJiBvbmNva2JfYW5ub3RbZ2VuZSwgIk9uY29LQiJdID09ICJZZXMiICkgewogICAgICAgICAgZ3JvdXAueiRleHRfbGlua3NbIGdyb3VwLnokR2VuZSA9PSBnZW5lIF0gPC0gcGFzdGUoIGdyb3VwLnokZXh0X2xpbmtzWyBncm91cC56JEdlbmU9PWdlbmUgXSAsIHBhc3RlMCgiPGEgaHJlZj0naHR0cDovL29uY29rYi5vcmcvIy9nZW5lLyIsIGdlbmUsICInIHRhcmdldD0nX2JsYW5rJz5PbmNvS0I8L2E+IiksIHNlcCA9ICIsICIpCiAgICAgICAgfQogICAgICB9CiAgICAgIAogICAgICAjIyMjIyBQcm92aWRlIGxpbmsgdG8gQ0lWaUMgZGF0YWJhc2UgZHJ1Z2dhYmxlIGdlbmVzICggaHR0cHM6Ly9jaXZpY2RiLm9yZyApCiAgICAgIGlmICggZ2VuZSAlaW4lIGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV0kZ2VuZSApIHsKICAgICAgICBncm91cC56JGV4dF9saW5rc1sgZ3JvdXAueiRHZW5lPT1nZW5lIF0gPC0gcGFzdGUoIGdyb3VwLnokZXh0X2xpbmtzWyBncm91cC56JEdlbmU9PWdlbmUgXSAsIHBhc3RlMCgiPGEgaHJlZj0nIiwgdW5pcXVlKGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV1bIGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV0kZ2VuZSA9PSBnZW5lICwgImdlbmVfY2l2aWNfdXJsIl0pLCAiJyB0YXJnZXQ9J19ibGFuayc+Q0lWaUM8L2E+IiksIHNlcCA9ICIsICIpCiAgICAgIH0KICAgIH0KICAgIAogICAgbmFtZXMoZ3JvdXAueikgPC0gZ3N1YigiZXh0X2xpbmtzIiwgIkV4dGVybmFsIHJlc291cmNlcyIsIG5hbWVzKGdyb3VwLnopKQogIH0KICAKICAjIyMjIyBBdHRhY2ggbGlua3MgdG8gR2VuZUNhcmRzIGFuZCBFbnNlbWJsIChpZiBwcm92aWRlZCkuIEhlcmUgd2UgYXNzdW1lIHRoYXQgZ2VuZSBuYW1lcyBhcmUKICBmb3IgKCBnZW5lIGluIGdlbmVzICkgewogICAgaWYgKCAiRU5TRU1CTCIgJWluJSBuYW1lcyhncm91cC56KSApIHsKICAgICAgICBpZiAoICFpcy5uYShncm91cC56JEVOU0VNQkxbIGdyb3VwLnokR2VuZT09Z2VuZSBdKSApIHsKICAgICAgICAgIAogICAgICAgICAgZ3JvdXAueiRFTlNFTUJMWyBncm91cC56JEdlbmU9PWdlbmUgXSA8LSBwYXN0ZTAoIjxhIGhyZWY9J2h0dHA6Ly9lbnNlbWJsLm9yZy9Ib21vX3NhcGllbnMvR2VuZS9TdW1tYXJ5P2RiPWNvcmU7Zz0iLCBncm91cC56JEVOU0VNQkxbIGdyb3VwLnokR2VuZT09Z2VuZV0sICInIHRhcmdldD0nX2JsYW5rJz4iLCBncm91cC56JEVOU0VNQkxbIGdyb3VwLnokR2VuZSA9PSBnZW5lIF0sICI8L2E+IikKICAgICAgfQogICAgfQogICAgCiAgICBncm91cC56JEdlbmVbIGdyb3VwLnokR2VuZT09Z2VuZSBdIDwtIHBhc3RlMCgiPGEgaHJlZj0naHR0cHM6Ly93d3cuZ2VuZWNhcmRzLm9yZy9jZ2ktYmluL2NhcmRkaXNwLnBsP2dlbmU9IiwgZ2VuZSwgIicgdGFyZ2V0PSdfYmxhbmsnPiIsIGdlbmUsICI8L2E+IikKICB9CgogICMjIyMjIE9yZGVyIHRoZSBkYXRhIGJ5IENOIHZhbHVlcyAodG8gYWxsb3cgZmlsdGVyaW5nIGJhc2VkIG9uIENOIGluZm9ybWF0aW9uKSBhbmQgdGhlbiBieSB0aGUgaGlnaGVzdCBhYnNvbHV0ZSB2YWx1ZXMgZm9yIFBhdGllbnQgdnMgW2NvbXBfY2FuY2VyXSBkaWZmZXJlbmNlICh0byBhbGxvdyBmaWx0ZXJpbmcgYmFzZWQgb24gei1zY29yZSBkaWZmZXJlbmNlcykKICBpZiAoICFpcy5udWxsKGNuX2RhdGEpICYmIGxlbmd0aChnZW5lcykgPiAwICkgewogICAgIyMjIyMgR2V0IHRoZSBwb3NpdGlvbiBvZiAiUGF0aWVudCAoQ04pIiBjb2x1bW4KICAgIGNvbF9pZHggPC0gZ3JlcCgiUGF0aWVudCAoQ04pIiwgbmFtZXMoZ3JvdXAueiksIGZpeGVkID0gVFJVRSkKICAgIGdyb3VwLnogPC0gZ3JvdXAuelsgb3JkZXIoYWJzKGdyb3VwLnpbLCAiRGlmZiJdKSwgIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQogICAgZ3JvdXAueiA8LSBncm91cC56WyBvcmRlcihncm91cC56WyAsY29sX2lkeCBdLCAgZGVjcmVhc2luZyA9IGNuX2RlY3JlYXNlKSwgXQogICAgCiAgIyMjIyMgT3JkZXIgdGhlIGRhdGEgYnkgaW5jcmVhc2luZyBUSUVSIGNhdGVnb3J5ICh0byBhbGxvdyBmaWx0ZXJpbmcgYmFzZWQgb24gdGllciBpbmZvcm1hdGlvbikgYW5kIHRoZW4gYnkgdGhlIGhpZ2hlc3QgYWJzb2x1dGUgdmFsdWVzIGZvciAiRGlmZiIgZGlmZmVyZW5jZSAodG8gYWxsb3cgZmlsdGVyaW5nIGJhc2VkIG9uIHotc2NvcmUgZGlmZmVyZW5jZXMpCiAgfSBlbHNlIGlmICAoICFpcy5udWxsKG11dF9hbm5vdCkgJiYgbGVuZ3RoKGdlbmVzKSA+IDAgKSB7CiAgICBncm91cC56IDwtIGdyb3VwLnpbIG9yZGVyKGFicyhncm91cC56WywgIkRpZmYiXSksICBkZWNyZWFzaW5nID0gVFJVRSksIF0KICAgIGdyb3VwLnogPC0gZ3JvdXAuelsgb3JkZXIoZ3JvdXAueiRUSUVSKSwgXQogICAgCiAgIyMjIyMgT3JkZXIgdGhlIGRhdGEgYnkgTUFOVEEgaW5jcmVhc2luZyBUaWVyICh0byBwcmlvcml0aXNlIFNWcywgYmFzZWQgb24gaHR0cHM6Ly9naXRodWIuY29tL0FzdHJhWmVuZWNhLU5HUy9zaW1wbGVfc3ZfYW5ub3RhdGlvbi9ibG9iL21hc3Rlci9zaW1wbGVfc3ZfYW5ub3RhdGlvbi5weSksIGV2ZW50IHR5cGUgYW5kIHRoZW4gYnkgdGhlIGhpZ2hlc3QgYWJzb2x1dGUgdmFsdWVzIGZvciBQYXRpZW50IHZzIFtjb21wX2NhbmNlcl0gZGlmZmVyZW5jZQogIH0gZWxzZSBpZiAgKCAhaXMubnVsbChzdl9kYXRhKSAmJiBsZW5ndGgoZ2VuZXMpID4gMCApIHsKICAgIGdyb3VwLnogPC0gZ3JvdXAuelsgb3JkZXIoYWJzKGdyb3VwLnpbLCAiRGlmZiJdKSwgIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQogICAgZ3JvdXAueiA8LSBncm91cC56WyBvcmRlcihncm91cC56JCJGdXNpb24gZ2VuZXMiLCAgZGVjcmVhc2luZyA9IFRSVUUpLCBdCiAgICBncm91cC56IDwtIGdyb3VwLnpbIG9yZGVyKGdyb3VwLnokVGllciksIF0KICAgIAogICMjIyMjIE90aGVyd2lzZSBvcmRlciB0YWJsZSBieSB0aGUgaGlnaGVzdCBhYnNvbHV0ZSB2YWx1ZXMgZm9yIFBhdGllbnQgdnMgW2NvbXBfY2FuY2VyXSBkaWZmZXJlbmNlCiAgfSBlbHNlIGlmICggbGVuZ3RoKGdlbmVzKSA+IDAgKSB7CiAgICBncm91cC56IDwtIGdyb3VwLnpbIG9yZGVyKGFicyhncm91cC56WywgIkRpZmYiXSksICBkZWNyZWFzaW5nID0gVFJVRSksIF0KICB9CiAgCiAgIyMjIyMgUmVtb3ZlIHRoZSBpbnRlcm5hbCByZWZlcmVuY2UgY29ob3J0IGNvbHVtbiBpZiB0aGUgcGF0aWVudCBzYW1wbGVzIG9yaWdpbnMgZnJvbSBvdGhlciB0aXNzdWUuIE9mIG5vdGUsIHRoZSBpbnRlcm5hbCByZWZlcmVuY2UgY29ob3J0IHdhcyBvbmx5IHVzZWQgdG8gcHJvY2VzcyB0aGUgaW4taG91c2UgZGF0YSAoaW5jbHVkaW5nIHRoZSBpbnZlc3RpZ2F0ZWQgcGF0aWVudCBzYW1wbGUpIGFuZCB0byBjb3JyZWN0IGJhdGNoLWVmZmVjdHMKICBpZiAoIGNvbXBfY2FuY2VyICE9IGludF9jYW5jZXIgKSB7CiAgICAgIGdyb3VwLnogPC0gZ3JvdXAuelsgLCBuYW1lcyhncm91cC56KSAlIWluJSBpbnRfY2FuY2VyIF0KICAgICAgdGFyZ2V0cy5saXN0WyBtYXRjaChpbnRfY2FuY2VyLCB0YXJnZXRzLmxpc3QpIF0gPC0gIlBhdGllbnQiCiAgICAgIAogICAgICAjIyMjIyBHZXQgdGhlIHBvc2l0aW9uIG9mICJEaWZmIiBjb2x1bW4KICAgICAgZGlmZl9jb2xfaWR4IDwtIGdyZXAoIkRpZmYiLCBuYW1lcyhncm91cC56KSwgZml4ZWQgPSBUUlVFKQogICAgICAKICB9IGVsc2UgewogICAgICAjIyMjIyBHZXQgdGhlIHBvc2l0aW9uIG9mICJEaWZmIiBjb2x1bW4KICAgICAgZGlmZl9jb2xfaWR4IDwtIGdyZXAoIkRpZmYiLCBuYW1lcyhncm91cC56KSwgZml4ZWQgPSBUUlVFKQogICAgICBuYW1lcyhncm91cC56KVsgbWF0Y2goIkRpZmYiLCBuYW1lcyhncm91cC56KSkgXSA8LSBwYXN0ZTAoIlBhdGllbnQgdnMgIiwgY29tcF9jYW5jZXIpCiAgfQogIAogICMjIyMjIExpbWl0IHRoZSBvcmRlcmVkIHRhYmxlIHRvIG1heGltdW0gb2YgMjAwMCBlbnRyaWVzIGlmICJrZWVwX2FsbCIgaXMgc2V0IHRvIEZBTFNFIChkZWZhdWx0KQogIGlmICggbnJvdyhncm91cC56KSA+IDIwMDAgJiYgIWtlZXBfYWxsICkgewogICAgZ3JvdXAueiA8LSBncm91cC56WyAxOjIwMDAsIF0KICB9CiAgCiAgIyMjIyMgRGVmaW5lIHRhYmxlIGhlaWdodAogIGlmICggbnJvdyhncm91cC56KSA9PSAyICkgewogICAgdGFibGVfaGVpZ2h0IDwtIDIzMAogICAgc2Nyb2xsWSA8LSAiNjdweCIKICB9IGVsc2UgewogICAgc2Nyb2xsWSA8LSAiMTY3cHgiCiAgICB0YWJsZV9oZWlnaHQgPC0gMzE4CiAgfQogIAogICMjIyMjIEdlbmVyYXRlIGEgdGFibGUgd2l0aCBnZW5lcyBhbm5vdGF0aW9ucyBhbmQgY29sb3VyZWQgZXhwcmVzc2lvbiB2YWx1ZXMgaW4gZWFjaCBncm91cAogIGlmICggIWlzLm51bGwoY25fZGF0YSkgKSB7CiAgICBkdC50YWJsZSA8LSBEVDo6ZGF0YXRhYmxlKCBkYXRhID0gZ3JvdXAuelssIG5hbWVzKGdyb3VwLnopICUhaW4lIGMoIlNZTUJPTCIsICJTRCIpXSwgZmlsdGVyPSJub25lIiwgcm93bmFtZXMgPSBGQUxTRSwgZXh0ZW5zaW9ucyA9IGMoJ0J1dHRvbnMnLCdTY3JvbGxlcicpLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIGRvbSA9ICdCZnJ0aXAnLCBidXR0b25zID0gYygnZXhjZWwnLCAnY3N2JywgJ3BkZicsJ2NvcHknLCdjb2x2aXMnKSwgc2Nyb2xsWCA9IFRSVUUsIHNjcm9sbENvbGxhcHNlID0gVFJVRSwgZGVmZXJSZW5kZXIgPSBUUlVFLCBzY3JvbGxZID0gc2Nyb2xsWSwgc2Nyb2xsZXIgPSBUUlVFKSwgd2lkdGggPSA4MDAsIGhlaWdodCA9IHRhYmxlX2hlaWdodCwgY2FwdGlvbiA9IGh0bWx0b29sczo6dGFncyRjYXB0aW9uKCBzdHlsZSA9ICdjYXB0aW9uLXNpZGU6IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgY29sb3I6Z3JleTsgZm9udC1zaXplOjEwMCUgOycpLCBlc2NhcGUgPSBGQUxTRSkgJT4lCiAgICAgIERUOjpmb3JtYXRTdHlsZSggY29sdW1ucyA9IG5hbWVzKGdyb3VwLnopW25hbWVzKGdyb3VwLnopICUhaW4lIGMoIlNZTUJPTCIsICJTRCIpXSwgYGZvbnQtc2l6ZWAgPSAnMTJweCcsICd0ZXh0LWFsaWduJyA9ICdjZW50ZXInICkgJT4lCiAgICAgIAogICAgICAjIyMjIyBDb2xvdXIgY2VsbHMgYWNjb3JkaW5nIHRvIHRoZSBleHByZXNzaW9uIHZhbHVlcyBxdWFudGlsZXMgaW4gZWFjaCBncm91cAogICAgICBEVDo6Zm9ybWF0U3R5bGUoY29sdW1ucyA9IHRhcmdldHMubGlzdFsxXSwgCiAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3IgPSBEVDo6c3R5bGVJbnRlcnZhbChicmtzLnFbW3RhcmdldHMubGlzdFsxXV1dLCBjbHJzLnFbW3RhcmdldHMubGlzdFsxXV1dKSkgJT4lCiAgICAgIERUOjpmb3JtYXRTdHlsZShjb2x1bW5zID0gdGFyZ2V0cy5saXN0WzJdLCAKICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRDb2xvciA9IERUOjpzdHlsZUludGVydmFsKGJya3MucVtbdGFyZ2V0cy5saXN0WzJdXV0sIGNscnMucVtbdGFyZ2V0cy5saXN0WzJdXV0pKSAlPiUKICAgICAgRFQ6OmZvcm1hdFN0eWxlKGNvbHVtbnMgPSB0YXJnZXRzLmxpc3RbM10sIAogICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlSW50ZXJ2YWwoYnJrcy5xW1t0YXJnZXRzLmxpc3RbM11dXSwgY2xycy5xW1t0YXJnZXRzLmxpc3RbM11dXSkpICU+JQogICAgICBEVDo6Zm9ybWF0U3R5bGUoY29sdW1ucyA9IG5hbWVzKGdyb3VwLnopW2RpZmZfY29sX2lkeF0sIAogICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlSW50ZXJ2YWwoYnJrcy5xW1siRGlmZiJdXSwgY2xycy5xW1siRGlmZiJdXSkpICU+JQogICAgICBEVDo6Zm9ybWF0U3R5bGUoY29sdW1ucyA9ICJQYXRpZW50IChDTikiLCBiYWNrZ3JvdW5kID0gRFQ6OnN0eWxlQ29sb3JCYXIoY25fcmFuZ2UsICdsaWdodGJsdWUnKSwgYmFja2dyb3VuZFNpemUgPSAnOTglIDg4JScsIGJhY2tncm91bmRSZXBlYXQgPSAnbm8tcmVwZWF0JywgYmFja2dyb3VuZFBvc2l0aW9uID0gJ2NlbnRlcicpCiAgICAKICAjIyMjIyBHZW5lcmF0ZSBhIHRhYmxlIHdpdGggZ2VuZXMgYW5ub3RhdGlvbnMgYW5kIGNvbG91cmVkIGV4cHJlc3Npb24gdmFsdWVzIGluIGVhY2ggZ3JvdXAKICB9IGVsc2UgewogICAgZHQudGFibGUgPC0gRFQ6OmRhdGF0YWJsZSggZGF0YSA9IGdyb3VwLnpbLCBuYW1lcyhncm91cC56KSAlIWluJSBjKCJTWU1CT0wiLCAiU0QiKV0sIGZpbHRlcj0ibm9uZSIsIHJvd25hbWVzID0gRkFMU0UsIGV4dGVuc2lvbnMgPSBjKCdCdXR0b25zJywnU2Nyb2xsZXInKSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEwLCBkb20gPSAnQmZydGlwJywgYnV0dG9ucyA9IGMoJ2V4Y2VsJywgJ2NzdicsICdwZGYnLCdjb3B5JywnY29sdmlzJyksIHNjcm9sbFggPSBUUlVFLCBzY3JvbGxDb2xsYXBzZSA9IFRSVUUsIGRlZmVyUmVuZGVyID0gVFJVRSwgc2Nyb2xsWSA9IHNjcm9sbFksIHNjcm9sbGVyID0gVFJVRSksIHdpZHRoID0gODAwLCBoZWlnaHQgPSB0YWJsZV9oZWlnaHQsIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbiggc3R5bGUgPSAnY2FwdGlvbi1zaWRlOiB0b3A7IHRleHQtYWxpZ246IGxlZnQ7IGNvbG9yOmdyZXk7IGZvbnQtc2l6ZToxMDAlIDsnKSwgZXNjYXBlID0gRkFMU0UpICU+JQogICAgICBEVDo6Zm9ybWF0U3R5bGUoIGNvbHVtbnMgPSBuYW1lcyhncm91cC56KVtuYW1lcyhncm91cC56KSAlIWluJSBjKCJTWU1CT0wiLCAiU0QiKV0sIGBmb250LXNpemVgID0gJzEycHgnLCAndGV4dC1hbGlnbicgPSAnY2VudGVyJyApICU+JQogICAgICAKICAgICAgIyMjIyMgQ29sb3VyIGNlbGxzIGFjY29yZGluZyB0byB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgcXVhbnRpbGVzIGluIGVhY2ggZ3JvdXAKICAgICAgRFQ6OmZvcm1hdFN0eWxlKGNvbHVtbnMgPSB0YXJnZXRzLmxpc3RbMV0sIAogICAgICAgICAgICAgICAgICAgICAgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlSW50ZXJ2YWwoYnJrcy5xW1t0YXJnZXRzLmxpc3RbMV1dXSwgY2xycy5xW1t0YXJnZXRzLmxpc3RbMV1dXSkpICU+JQogICAgICBEVDo6Zm9ybWF0U3R5bGUoY29sdW1ucyA9IHRhcmdldHMubGlzdFsyXSwgCiAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3IgPSBEVDo6c3R5bGVJbnRlcnZhbChicmtzLnFbW3RhcmdldHMubGlzdFsyXV1dLCBjbHJzLnFbW3RhcmdldHMubGlzdFsyXV1dKSkgJT4lCiAgICAgIERUOjpmb3JtYXRTdHlsZShjb2x1bW5zID0gdGFyZ2V0cy5saXN0WzNdLCAKICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRDb2xvciA9IERUOjpzdHlsZUludGVydmFsKGJya3MucVtbdGFyZ2V0cy5saXN0WzNdXV0sIGNscnMucVtbdGFyZ2V0cy5saXN0WzNdXV0pKSAlPiUKICAgICAgRFQ6OmZvcm1hdFN0eWxlKGNvbHVtbnMgPSBuYW1lcyhncm91cC56KVtkaWZmX2NvbF9pZHhdLCAKICAgICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRDb2xvciA9IERUOjpzdHlsZUludGVydmFsKGJya3MucVtbIkRpZmYiXV0sIGNscnMucVtbIkRpZmYiXV0pKQogIH0KICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UgYW5kIHJldHVybiBvdXRwdXQKICBybShnZW5lcywgZGF0YSwgY25fZGF0YSwgc3ZfZGF0YSwgdGFyZ2V0cywgc2FtcGxlTmFtZSwgZ2VuZXNfYW5ub3QsIG9uY29rYl9hbm5vdCwgY2FuY2VyX2dlbmVzLCBtdXRfYW5ub3QsIGZ1c2lvbl9nZW5lcywgZ2VuZXMuYWJzZW50LCB0YXJnZXRzLmxpc3QsIGdyb3VwLnN0YXRzLCBicmtzLnEsIGNscnMucSkKICAKICByZXR1cm4oIGxpc3QoZHQudGFibGUsICBncm91cC56KSApCn0KCiMjIyMjIEdlbmVyYXRlIHRhYmxlIHdpdGggZHJ1Z3MgdGFyZ2V0aW5nIHNlbGVjdGVkIHNldCBvZiBnZW5lcyB1c2luZyBpbmZvIGZyb20gQ0lWaUMgZGF0YWJhc2UgKGh0dHBzOi8vY2l2aWNkYi5vcmcvKQpjaXZpY0RydWdUYWJsZSA8LSBmdW5jdGlvbihnZW5lcywgY2l2aWNfdmFyX3N1bW1hcmllcywgY2l2aWNfY2xpbl9ldmlkLCBldmlkX3R5cGUgPSAiUHJlZGljdGl2ZSIsIHZhcl90eXBlID0gTlVMTCkgewogIAogICMjIyMjIEluaXRpYWxpemUgZGF0YSBmcmFtZSB0byB0aGUgYWJvdXQgZHJ1Zy10YXJnZXQgaW5mbyBmcm9tIENJVmlDCiAgZHJ1Zy5pbmZvIDwtIHNldE5hbWVzKGRhdGEuZnJhbWUobWF0cml4KG5jb2wgPSAxOCwgbnJvdyA9IDApKSwgYygiR2VuZSIsICJWYXJpYW50IiwgInZhcmlhbnRfdHlwZXMiLCAiZHJ1Z3MiLCAibmN0X2lkcyIsICJldmlkZW5jZV9sZXZlbCIsICJldmlkZW5jZV90eXBlIiwgImV2aWRlbmNlX2RpcmVjdGlvbiIsICJjbGluaWNhbF9zaWduaWZpY2FuY2UiLCAicmF0aW5nIiwgImNpdmljX2FjdGlvbmFiaWxpdHlfc2NvcmUiLCAiRGlzZWFzZSIsICJwaGVub3R5cGVzIiwgInB1Ym1lZF9pZCIsICJ2YXJpYW50X29yaWdpbiIsICJyZXByZXNlbnRhdGl2ZV90cmFuc2NyaXB0IiwgInJlcHJlc2VudGF0aXZlX3RyYW5zY3JpcHQyIiwgImxhc3RfcmV2aWV3X2RhdGUiKSkKICAKICBldmlkX2xldmVscyA8LSBsaXN0KCJBIiA9ICJBOiBWYWxpZGF0ZWQgYXNzb2NpYXRpb24iLCAiQiIgPSAiQjogQ2xpbmljYWwgZXZpZGVuY2UiLCAiQyIgPSAiQzogQ2FzZSBzdHVkeSIsICJEIiA9ICJEOiBQcmVjbGluaWNhbCBldmlkZW5jZSIsICJFIiA9ICJFOiBJbmZlcmVudGlhbCBhc3NvY2lhdGlvbiIpCiAgCiAgIyMjIyMgTG9vcCB0aG91cmdoIGVhY2ggZ2VuZSBhbmQgY2hlY2sgaWYgdGhleSBhcmUgZHJ1Z2dhYmxlCiAgZm9yICggZ2VuZSBpbiBnZW5lcykgewogICAgIyMjIyMgR2V0IHN1bW1hcnkgaW5mbyBhYm91dCBkcnVnZ2FibGUgZ2VuZXMKICAgIGlmICggZ2VuZSAlaW4lIGNpdmljX2NsaW5fZXZpZCRnZW5lICkgewogICAgICAjIyMjIyBFeHRyYWN0IGluZm8gYWJvdXQgYWxsIHJlcG9ydGVkIHZhcmlhbnRzJ3MgY2xpbmljYWwgZXZpZGVuY2UgZm9yIHF1ZXJpZWQgZ2VuZQogICAgICBjbGluLmV2aWQuaW5mbyA8LSBjaXZpY19jbGluX2V2aWRbIGNpdmljX2NsaW5fZXZpZCRnZW5lID09IGdlbmUgLCBdCgogICAgICAjIyMjIyBVc2UgbW9yZSBkZXNjcmlwdGl2ZSBldmlkZW5jZSBsZXZlbCBpbmZvCiAgICAgIGZvciAoIGxldmVsIGluIHVuaXF1ZShjbGluLmV2aWQuaW5mbyRldmlkZW5jZV9sZXZlbCkgKSB7CiAgICAgICAgY2xpbi5ldmlkLmluZm8kZXZpZGVuY2VfbGV2ZWxbIGNsaW4uZXZpZC5pbmZvJGV2aWRlbmNlX2xldmVsID09IGxldmVsIF0gPC0gZXZpZF9sZXZlbHNbWyBsZXZlbCBdXQogICAgICB9CiAgICAgIAogICAgICAjIyMjIyBTdWJzZXQgdGFibGUgdG8gaW5jbHVkZSBvbmx5IHZhcmlhbnRzIHdpdGggdGhlIGV2aWRlbmNlIHR5cGUgb2YgaW50ZXJlc3QKICAgICAgY2xpbi5ldmlkLmluZm8gPC0gY2xpbi5ldmlkLmluZm9bIGNsaW4uZXZpZC5pbmZvJGV2aWRlbmNlX3R5cGUgPT0gZXZpZF90eXBlLCAgXQogICAgICAgIAogICAgICBpZiAoIG5yb3coY2xpbi5ldmlkLmluZm8pID4gMCApIHsKICAgICAgICAjIyMjIyBQcm92aWRlIGxpbmsgdG8gQ0lWaUMgY2xpbmljYWwgZXZpZGVuY2Ugc3VtbWFyeQogICAgICAgIGNsaW4uZXZpZC5pbmZvJGRydWdzIDwtIHBhc3RlMCgiPGEgaHJlZj0nIiwgY2xpbi5ldmlkLmluZm8kZXZpZGVuY2VfY2l2aWNfdXJsLCAiJyB0YXJnZXQ9J19ibGFuayc+IiwgY2xpbi5ldmlkLmluZm8kZHJ1Z3MsICI8L2E+IikKICAgICAgICAKICAgICAgICAjIyMjIyBQcm92aWRlIGxpbmsgdG8gQ0lWaUMgY2xpbmljYWwgZXZpZGVuY2Ugc3VtbWFyeQogICAgICAgIGNsaW4uZXZpZC5pbmZvJGV2aWRlbmNlX3R5cGUgPC0gcGFzdGUwKCI8YSBocmVmPSciLCBjbGluLmV2aWQuaW5mbyRldmlkZW5jZV9jaXZpY191cmwsICInIHRhcmdldD0nX2JsYW5rJz4iLCBjbGluLmV2aWQuaW5mbyRldmlkZW5jZV90eXBlLCAiPC9hPiIpCiAgICAgICAgCiAgICAgICAgIyMjIyMgUHJvdmlkZSBsaW5rIHRvIENJVmlDIGdlbmUgc3VtbWFyeQogICAgICAgIGNsaW4uZXZpZC5pbmZvJGdlbmVfY2l2aWNfdXJsIDwtIHBhc3RlMCgiPGEgaHJlZj0nIiwgY2xpbi5ldmlkLmluZm8kZ2VuZV9jaXZpY191cmwsICInIHRhcmdldD0nX2JsYW5rJz4iLCBnZW5lLCAiPC9hPiIpCiAgICAgICAgbmFtZXMoY2xpbi5ldmlkLmluZm8pWyBuYW1lcyhjbGluLmV2aWQuaW5mbykgPT0iZ2VuZV9jaXZpY191cmwiIF0gPC0gIkdlbmUiCiAgICAgICAgCiAgICAgICAgIyMjIyMgUHJvdmlkZSBsaW5rIHRvIENJVmlDIHZhcmlhbnRzIHN1bW1hcnkKICAgICAgICBjbGluLmV2aWQuaW5mbyR2YXJpYW50X2NpdmljX3VybCA8LSBwYXN0ZTAoIjxhIGhyZWY9JyIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnRfY2l2aWNfdXJsLCAiJyB0YXJnZXQ9J19ibGFuayc+IiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgIjwvYT4iKQogICAgICAgIG5hbWVzKGNsaW4uZXZpZC5pbmZvKVsgbmFtZXMoY2xpbi5ldmlkLmluZm8pID09InZhcmlhbnRfY2l2aWNfdXJsIiBdIDwtICJWYXJpYW50IgogICAgICAgIAogICAgICAgICMjIyMjIFByb3ZpZGUgbGluayB0byBDbGluaWNhbFRyaWFscy5nb3YgdmFyaWFudHMgc3VtbWFyeSBiYXNlZCBvbiBOQ1QgSURzCiAgICAgICAgZm9yICggbmN0X2lkIGluIGNsaW4uZXZpZC5pbmZvJG5jdF9pZHMgKSB7CiAgICAgICAgICBpZiAoICFpcy5lbXB0eShuY3RfaWQpICkgewogICAgICAgICAgICAKICAgICAgICAgICAgIyMjIyMgRGVhbCB3aXRoIG11bHRpcGxlIE5DVCBJRHMgKHNlcGFyYXRlZCBieSBjb21tYSkKICAgICAgICAgICAgbmN0X2lkX3VybCA8LSBnc3ViKCIgJyIgLCAiJyIsIHBhc3RlKGdzdWIoIi8gIiAsICIvIiwgcGFzdGUoIjxhIGhyZWY9J2h0dHBzOi8vY2xpbmljYWx0cmlhbHMuZ292L2N0Mi9zaG93LyIsIHVubGlzdChzdHJzcGxpdChuY3RfaWQsIHNwbGl0PSIsIiwgZml4ZWQ9VFJVRSkpICwgIicgdGFyZ2V0PSdfYmxhbmsnPiIsIHVubGlzdChzdHJzcGxpdChuY3RfaWQsIHNwbGl0PSIsIiwgZml4ZWQ9VFJVRSkpLCAiPC9hPiIpKSwgY29sbGFwc2UgPSAiLCAiKSkKICAgICAgICAgICAgY2xpbi5ldmlkLmluZm8kbmN0X2lkc1sgY2xpbi5ldmlkLmluZm8kbmN0X2lkcz09bmN0X2lkIF0gPC0gbmN0X2lkX3VybAogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICAKICAgICAgICAjIyMjIyBQcm92aWRlIGxpbmsgdG8gUHViTWVkIHZhcmlhbnRzIHN1bW1hcnkKICAgICAgICBjbGluLmV2aWQuaW5mbyRwdWJtZWRfaWQgPC0gcGFzdGUwKCI8YSBocmVmPSdodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8iLCBjbGluLmV2aWQuaW5mbyRwdWJtZWRfaWQsICInIHRhcmdldD0nX2JsYW5rJz4iLCBjbGluLmV2aWQuaW5mbyRwdWJtZWRfaWQsICI8L2E+IikKICAgICAgICAKICAgICAgICAjIyMjIyBQcm92aWRlIGxpbmsgdG8gRGlzZWFzZSBPbnRvbG9neQogICAgICAgIGNsaW4uZXZpZC5pbmZvJGRvaWQgPC0gcGFzdGUwKCI8YSBocmVmPSdodHRwOi8vd3d3LmRpc2Vhc2Utb250b2xvZ3kub3JnLz9pZD1ET0lEOiIsIGNsaW4uZXZpZC5pbmZvJGRvaWQsICInIHRhcmdldD0nX2JsYW5rJz4iLCBjbGluLmV2aWQuaW5mbyRkaXNlYXNlLCAiPC9hPiIpCiAgICAgICAgbmFtZXMoY2xpbi5ldmlkLmluZm8pWyBuYW1lcyhjbGluLmV2aWQuaW5mbykgPT0iZG9pZCIgXSA8LSAiRGlzZWFzZSIKICAgICAgICAKICAgICAgICAjIyMjIyBFeHRyYWN0IGluZm8gYWJvdXQgYWxsIHZhcmlhbnRzIGl0IHRoYXQgZ2VuZQogICAgICAgIHZhci5pbmZvIDwtIGNpdmljX3Zhcl9zdW1tYXJpZXNbIGNpdmljX3Zhcl9zdW1tYXJpZXMkZ2VuZSA9PSBnZW5lICwgXQogICAgICAgIHZhci5pbmZvIDwtIHZhci5pbmZvWywgYygidmFyaWFudCIsICJ2YXJpYW50X3R5cGVzIiwgImNpdmljX2FjdGlvbmFiaWxpdHlfc2NvcmUiKV0KICAgICAgICB2YXIuaW5mb1ssInZhcmlhbnRfdHlwZXMiXSA8LSBnc3ViKCJfIiwgIiAiLCB2YXIuaW5mb1ssInZhcmlhbnRfdHlwZXMiXSkKICAgICAgICB2YXIuaW5mb1ssInZhcmlhbnRfdHlwZXMiXSA8LSBnc3ViKCIsIiwgIiwgIiwgdmFyLmluZm9bLCJ2YXJpYW50X3R5cGVzIl0pCiAgICAgICAgCiAgICAgICAgIyMjIyMgTWVyZ2UgYWJvdXQgYWxsIHZhcmlhbnRzIGl0IHRoYXQgZ2VuZSBhbmQgY2xpbmljYWwgZXZpZGVuY2UgaW5mbwogICAgICAgIGNsaW4uZXZpZC5pbmZvIDwtIG1lcmdlKGNsaW4uZXZpZC5pbmZvLCB2YXIuaW5mbywgYnkgPSAidmFyaWFudCIsIGFsbC54ID0gVFJVRSkKICAgICAgICAKICAgICAgICAjIyMjIyBGaWx0ZXIgZHJ1ZyBtYXRjaGluZyBpbmZvIGRlcGVuZGluZyBvbiB0aGUgdmFyaWFudCB0eXBlCiAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBOVUxMCiAgICAgICAgCiAgICAgICAgIyMjIyMgUmVtb3ZlIGVudHJpZXMgY29udGFpbmluZyAiRVhQUkVTU0lPTiIsICJBTVBMSUZJQ0FUSU9OIiwgIkRFTEVUSU9OIiwgIk1FVEhZTEFUSU9OIiwgIldJTEQgVFlQRSIsICJGVVNJT04iLCAiQ09QWSIsICJSRUFSUkFOR0VNRU5UIiwgIlBIT1NQSE9SWUxBVElPTiIsICJUUkFOU0NSSVBUIiwgIkdBSU4iLCAiTE9TUyIKICAgICAgICBpZiAoICFpcy5udWxsKHZhcl90eXBlKSAmJiB2YXJfdHlwZSA9PSAibXV0YXRpb24iICkgewogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJFWFBSRVNTSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiQU1QTElGSUNBVElPTiIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIkRFTEVUSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiTUVUSFlMQVRJT04iLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJXSUxEIFRZUEUiLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJGVVNJT04iLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJSRUFSUkFOR0VNRU5UIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiUEhPU1BIT1JZTEFUSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiQ09QWSIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIlRSQU5TQ1JJUFQiLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJHQUlOIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiTE9TUyIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICAKICAgICAgICAgIGNsaW4uZXZpZC5pbmZvIDwtIGNsaW4uZXZpZC5pbmZvWyAtYyh1bmlxdWUodmFyX3R5cGUua2VlcCkpLCBdCiAgICAgICAgICAKICAgICAgICAjIyMjIyBLZWVwIG9ubHkgZW50cmllcyBjb250YWluaW5nICJFWFBSRVNTSU9OIiwgIkZVU0lPTiIsICJUUkFOU0NSSVBUIiwgIkFMVEVSQVRJT04iCiAgICAgICAgfSBlbHNlIGlmICggIWlzLm51bGwodmFyX3R5cGUpICYmIHZhcl90eXBlID09ICJleHByZXNzaW9uIiApIHsKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiRVhQUkVTU0lPTiIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIkZVU0lPTiIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIlRSQU5TQ1JJUFQiLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJBTFRFUkFUSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIAogICAgICAgICAgY2xpbi5ldmlkLmluZm8gPC0gY2xpbi5ldmlkLmluZm9bIGModW5pcXVlKHZhcl90eXBlLmtlZXApKSwgXQogICAgICAgICAgCiAgICAgICAgIyMjIyMgS2VlcCBvbmx5IGVudHJpZXMgY29udGFpbmluZyAiRlVTSU9OIiwgIkFMVEVSQVRJT04iLCAiW2dlbmVdLSIsICItW2dlbmVdIgogICAgICAgIH0gZWxzZSBpZiAoICFpcy5udWxsKHZhcl90eXBlKSAmJiB2YXJfdHlwZSA9PSAiZnVzaW9uIiApIHsKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiRlVTSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCBwYXN0ZTAoZ2VuZSwgIi0iKSwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCBwYXN0ZTAoIi0iLCBnZW5lKSwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiQUxURVJBVElPTiIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICAKICAgICAgICAgIGNsaW4uZXZpZC5pbmZvIDwtIGNsaW4uZXZpZC5pbmZvWyBjKHVuaXF1ZSh2YXJfdHlwZS5rZWVwKSksIF0KICAgICAgICAKICAgICAgICAjIyMjIyBLZWVwIG9ubHkgZW50cmllcyBjb250YWluaW5nICJBTVBMSUZJQ0FUSU9OIiwgIkNPUFkiLCAiR0FJTiIsICJBTFRFUkFUSU9OIgogICAgICAgIH0gZWxzZSBpZiAoICFpcy5udWxsKHZhcl90eXBlKSAmJiB2YXJfdHlwZSA9PSAiY29weV9nYWluIiApIHsKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiQU1QTElGSUNBVElPTiIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIkNPUFkiLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJHQUlOIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiQUxURVJBVElPTiIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICAKICAgICAgICAgIGNsaW4uZXZpZC5pbmZvIDwtIGNsaW4uZXZpZC5pbmZvWyBjKHVuaXF1ZSh2YXJfdHlwZS5rZWVwKSksIF0KICAgICAgICAKICAgICAgICAjIyMjIyBLZWVwIG9ubHkgZW50cmllcyBjb250YWluaW5nICJERUxFVElPTiIsICJDT1BZIiwgIkxPU1MiLCAiQUxURVJBVElPTiIKICAgICAgICB9IGVsc2UgaWYgKCAhaXMubnVsbCh2YXJfdHlwZSkgJiYgdmFyX3R5cGUgPT0gImNvcHlfbG9zcyIgKSB7CiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIkRFTEVUSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIHZhcl90eXBlLmtlZXAgPC0gYyh2YXJfdHlwZS5rZWVwLCBncmVwKCAiQ09QWSIsIGNsaW4uZXZpZC5pbmZvJHZhcmlhbnQsIGludmVydD1GQUxTRSwgaWdub3JlLmNhc2U9VFJVRSkpCiAgICAgICAgICB2YXJfdHlwZS5rZWVwIDwtIGModmFyX3R5cGUua2VlcCwgZ3JlcCggIkxPU1MiLCBjbGluLmV2aWQuaW5mbyR2YXJpYW50LCBpbnZlcnQ9RkFMU0UsIGlnbm9yZS5jYXNlPVRSVUUpKQogICAgICAgICAgdmFyX3R5cGUua2VlcCA8LSBjKHZhcl90eXBlLmtlZXAsIGdyZXAoICJBTFRFUkFUSU9OIiwgY2xpbi5ldmlkLmluZm8kdmFyaWFudCwgaW52ZXJ0PUZBTFNFLCBpZ25vcmUuY2FzZT1UUlVFKSkKICAgICAgICAgIAogICAgICAgICAgY2xpbi5ldmlkLmluZm8gPC0gY2xpbi5ldmlkLmluZm9bIGModW5pcXVlKHZhcl90eXBlLmtlZXApKSwgXQogICAgICAgIH0KICAgICAgfQogICAgICAKICAgICAgaWYgKCBucm93KGNsaW4uZXZpZC5pbmZvKSA+IDAgKSB7CiAgICAgICAgIyMjIyMgU3Vic2V0IHRhYmxlIHRvIGluY2x1ZGUgb25seSBtb3N0IGltcG9ydGFudCBpbmZvCiAgICAgICAgY2xpbi5ldmlkLmluZm8gPC0gY2xpbi5ldmlkLmluZm9bICwgbmFtZXMoZHJ1Zy5pbmZvKV0KICAgICAgICAKICAgICAgICAjIyMjIyBBZGQgZHJ1Z3MgaW5mbyBmb3Igc3Vic2VxdWVudCBnZW5lCiAgICAgICAgZHJ1Zy5pbmZvIDwtIHJiaW5kKGRydWcuaW5mbywgY2xpbi5ldmlkLmluZm8pCiAgICAgIH0KICAgIH0KICB9CiAgCiAgIyMjIyMgVXNlIG1vcmUgZnJpZW5kbHkgY29sdW1uIG5hbWVzIGZvciB0aGUgdGFibGUKICBuYW1lcyhkcnVnLmluZm8pIDwtIGMoIkdlbmUiLCAiVmFyaWFudCIsICJWYXJpYW50IHR5cGUiLCAiRHJ1Z3MiLCAiQ2xpbmljYWwgdHJpYWxzIiwgIkV2aWRlbmNlIGxldmVsIiwgIkV2aWRlbmNlIHR5cGUiLCAiRXZpZGVuY2UgZGlyZWN0aW9uIiwgIkNsaW5pY2FsIHNpZ25pZmljYW5jZSIsICJUcnVzdCByYXRpbmciLCAiQWN0aW9uYWJpbGl0eSBzY29yZSIsICJEaXNlYXNlIiwgIlBoZW5vdHlwZXMiLCAiUHViTWVkIElEIiwgICJWYXJpYW50IG9yaWdpbiIsICJSZXByZXNlbnRhdGl2ZSB0cmFuc2NyaXB0IiwgIlJlcHJlc2VudGF0aXZlIHRyYW5zY3JpcHQgMiIsICJSZXZpZXcgZGF0ZSIpCiAgCiAgIyMjIyMgTGltaXQgdGhlIGluZm8gdG8gZmV3ZXIgY29sdW1ucwogIGRydWcuaW5mbyA8LSBkcnVnLmluZm9bICwgYygiR2VuZSIsICJWYXJpYW50IiwgIlZhcmlhbnQgdHlwZSIsICJEcnVncyIsICJDbGluaWNhbCB0cmlhbHMiLCAiRXZpZGVuY2UgbGV2ZWwiLCAiRXZpZGVuY2UgZGlyZWN0aW9uIiwgIkNsaW5pY2FsIHNpZ25pZmljYW5jZSIsICJUcnVzdCByYXRpbmciLCAiQWN0aW9uYWJpbGl0eSBzY29yZSIsICJEaXNlYXNlIiwgIlBoZW5vdHlwZXMiLCAiUHViTWVkIElEIiwgICJSZXByZXNlbnRhdGl2ZSB0cmFuc2NyaXB0IiwgIlJlcHJlc2VudGF0aXZlIHRyYW5zY3JpcHQgMiIpXSAKICAKICAjIyMjIyBHZW5lcmF0ZSBhIHRhYmxlCiAgZHQudGFibGUgPC0gRFQ6OmRhdGF0YWJsZSggZGF0YSA9IGRydWcuaW5mbywgZmlsdGVyID0gIm5vbmUiLCByb3duYW1lcyA9IEZBTFNFLCBleHRlbnNpb25zID0gYygnQnV0dG9ucycsJ1Njcm9sbGVyJyksIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMCwgZG9tID0gJ0JmcnRpcCcsIGJ1dHRvbnMgPSBjKCdleGNlbCcsICdjc3YnLCAncGRmJywnY29weScsJ2NvbHZpcycpLCBzY3JvbGxYID0gVFJVRSwgZGVmZXJSZW5kZXIgPSBUUlVFLCBzY3JvbGxZID0gIjE2N3B4Iiwgc2Nyb2xsZXIgPSBUUlVFKSwgd2lkdGggPSA4MDAsIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbihzdHlsZSA9ICdjYXB0aW9uLXNpZGU6IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgY29sb3I6Z3JleTsgZm9udC1zaXplOjEwMCUgOycpLCBlc2NhcGUgPSBGQUxTRSkgJT4lCiAgICBEVDo6Zm9ybWF0U3R5bGUoIGNvbHVtbnMgPSBuYW1lcyhkcnVnLmluZm8pLCBgZm9udC1zaXplYCA9ICcxMnB4JywgJ3RleHQtYWxpZ24nID0gJ2NlbnRlcicgKSAlPiUKICAgICMjIyMjIENvbG91ciBjZWxscyBhY2NvcmRpbmcgdG8gZXZpZGVuY2UgbGV2ZWwgYW5kIHRydXN0IHJhdGluZwogICAgRFQ6OmZvcm1hdFN0eWxlKGNvbHVtbnMgPSAiRXZpZGVuY2UgbGV2ZWwiLCAKICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3IgPSBEVDo6c3R5bGVFcXVhbChjKCJBOiBWYWxpZGF0ZWQgYXNzb2NpYXRpb24iLCAiQjogQ2xpbmljYWwgZXZpZGVuY2UiLCAiQzogQ2FzZSBzdHVkeSIsICJEOiBQcmVjbGluaWNhbCBldmlkZW5jZSIsICJFOiBJbmZlcmVudGlhbCBhc3NvY2lhdGlvbiIpLCBjKCJtZWRpdW1zZWFncmVlbiIsICJkZWVwc2t5Ymx1ZSIsICJtZWRpdW1wdXJwbGUiLCAiZGFya29yYW5nZSIsICJjb3JhbCIpKSApICAlPiUKICAgIERUOjpmb3JtYXRTdHlsZShjb2x1bW5zID0gIlRydXN0IHJhdGluZyIsIAogICAgICAgICAgICAgICAgICAgIGJhY2tncm91bmRDb2xvciA9IERUOjpzdHlsZUVxdWFsKGMoMTo1KSwgYygiY29yYWwiLCAiYXp1cmUiLCAibGlnaHRza3libHVlIiwgInBhbGVncmVlbiIsICJtZWRpdW1zZWFncmVlbiIpKSApCiAgCiAgIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CiAgcm0oZ2VuZXMsIGNpdmljX3Zhcl9zdW1tYXJpZXMsIGNpdmljX2NsaW5fZXZpZCwgZXZpZF9sZXZlbHMsIGNsaW4uZXZpZC5pbmZvLCB2YXIuaW5mbywgdmFyX3R5cGUua2VlcCkKICByZXR1cm4oIGxpc3QoZHQudGFibGUsICBkcnVnLmluZm8pICkKfQoKIyMjIyMgQ29kZSBmcm9tIFVNQ0NSSVNFIHRvIHByaW9yaXRpc2UgU1YgZXZlbnRzICh2ZXJzaW9uIGZvciAiLXN2LXByaW9yaXRpemUtbWFudGEtcGFzcy50c3YiIGZpbGVzLCBodHRwczovL2dpdGh1Yi5jb20vdW1jY3IvdW1jY3Jpc2UvYmxvYi9tYXN0ZXIvdW1jY3Jpc2Uvcm1kX2ZpbGVzL2luZGV4LlJtZCkKc3ZfcHJpb3JpdGl6ZV9zaG9ydCA8LSBmdW5jdGlvbihzdl9maWxlKSB7CiAgCiAgc3ZfYWxsID0gTlVMTAogIAogIGlmIChsZW5ndGgocmVhZExpbmVzKGNvbiA9IHN2X2ZpbGUsIG4gPSAyKSkgPiAxKSB7CiAgICBzdl9hbGwgPC0gcmVhZHI6OnJlYWRfdHN2KHN2X2ZpbGUsIGNvbF9uYW1lcyA9IFRSVUUpICU+JQogICAgICB0aWR5cjo6dW5uZXN0KGFubm90YXRpb24gPSBzdHJzcGxpdChhbm5vdGF0aW9uLCAnLCcpKSAlPiUgIyBVbnBhY2sgbXVsdGlwbGUgYW5ub3RhdGlvbnMgcGVyIHJlZ2lvbgogICAgICB0aWR5cjo6c2VwYXJhdGUoYW5ub3RhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgIGMoJ0V2ZW50JywgJ0Fubm90YXRpb24nLCAnR2VuZScsICdUcmFuc2NyaXB0JywgJ1ByaW9yaXR5JywgJ1RpZXInKSwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICdcXHwnLCBjb252ZXJ0ID0gVFJVRSkgJT4lICMgVW5wYWNrIGFubm90YXRpb24gY29sdW1ucyAlPiUKICAgICAgZHBseXI6Om11dGF0ZShzdGFydCA9IGZvcm1hdChzdGFydCwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUKSwKICAgICAgICAgICAgICAgICAgICBlbmQgPSBmb3JtYXQoZW5kLCBiaWcubWFyayA9ICcsJywgdHJpbSA9IFQpKSAlPiUgCiAgICAgIGRwbHlyOjptdXRhdGUoTG9jYXRpb24gPSBzdHJfYyhjaHJvbSwgJzonLCBzdGFydCwgc2VwID0gJycpLAogICAgICAgICAgICAgICAgICAgIExvY2F0aW9uID0gaWZlbHNlKGlzLm5hKGVuZCksIExvY2F0aW9uLCBzdHJfYyhMb2NhdGlvbikpKSAlPiUKICAgICAgZHBseXI6Om11dGF0ZShTUiA9IHNwbGl0X3JlYWRfc3VwcG9ydCwgUFIgPSBwYWlyZWRfc3VwcG9ydF9QUikgJT4lCiAgICAgIGRwbHlyOjpzZWxlY3QoTG9jYXRpb24sIEdlbmUsIFByaW9yaXR5LCBUaWVyLCBBbm5vdGF0aW9uLCBFdmVudCwgU1IsIFBSKSAlPiUKICAgICAgZHBseXI6OmRpc3RpbmN0KCkKICAgICAgIyBkcGx5cjo6bXV0YXRlKENocm9tID0gZmFjdG9yKENocm9tLCBsZXZlbHMgPSBjKDE6MjIsICJYIiwgIlkiLCAiTVQiKSkpCiAgfSBlbHNlIHsKICAgIHdhcm5pbmcoJ05vIHByaW9yaXRpemVkIGV2ZW50cyBkZXRlY3RlZCcpCiAgfQogIHJldHVybiggc3ZfYWxsICkKfQoKIyMjIyMgQ29kZSBmcm9tIFVNQ0NSSVNFIHRvIHByaW9yaXRpc2UgU1YgZXZlbnRzICh2ZXJzaW9uIGZvciAiLW1hbnRhLnRzdiIgZmlsZXMgaHR0cHM6Ly9naXRodWIuY29tL3VtY2NyL3VtY2NyaXNlL2Jsb2IvbWFzdGVyL3VtY2NyaXNlL3JtZF9maWxlcy9pbmRleC5SbWQKc3ZfcHJpb3JpdGl6ZSA8LSBmdW5jdGlvbihzdl9maWxlKSB7CiAgCiAgc3ZfYWxsID0gTlVMTAoKICBpZiAobGVuZ3RoKHJlYWRMaW5lcyhjb24gPSBzdl9maWxlLCBuID0gMikpID4gMSkgewogICAgCiAgICAjIyMjIyBEdWUgdG8gY2hhbmdlcyBpbiBQVVJQTEUgb3V0cHV0IGZvcm1hdCB0aGVyZSBhcmUgdHdvIGV4cGVjdGVkIGNvbHVtbiBuYW1lcyBjb21iaW5hdGlvbnMKICAgIGlmICggYWxsKGMoIkFGX0JQSSIsICJBRl9QVVJQTEUiLCAiQ05fUFVSUExFIiwgIkNOX2NoYW5nZV9QVVJQTEUiLCAiUGxvaWR5X1BVUlBMRSIpICVpbiUgbmFtZXMocmVhZF90c3Yoc3ZfZmlsZSwgY29sX25hbWVzID0gVFJVRSkpKSApIHsKICAgIAogICAgICBzdl9hbGwgPC0gcmVhZHI6OnJlYWRfdHN2KHN2X2ZpbGUsIGNvbF9uYW1lcyA9IFRSVUUpICU+JQogICAgICAgIGRwbHlyOjpzZWxlY3QoLWNhbGxlciwgLXNhbXBsZSkgJT4lIAogICAgICAgIHNwbGl0X3N2X2ZpZWxkKEFGX0JQSSwgaXNfcGN0ID0gVCkgJT4lIAogICAgICAgIHNwbGl0X3N2X2ZpZWxkKEFGX1BVUlBMRSwgaXNfcGN0ID0gVCkgJT4lIAogICAgICAgIHNwbGl0X3N2X2ZpZWxkKENOX1BVUlBMRSkgJT4lIAogICAgICAgIHNwbGl0X3N2X2ZpZWxkKENOX2NoYW5nZV9QVVJQTEUpICU+JSAKICAgICAgICBkcGx5cjo6bXV0YXRlKAogICAgICAgICAgUGxvaWR5X1BVUlBMRSA9IGFzLmRvdWJsZShQbG9pZHlfUFVSUExFKSwKICAgICAgICAgIFBsb2lkeV9QVVJQTEUgPSBmb3JtYXQoUGxvaWR5X1BVUlBMRSwgbnNtYWxsID0gMikKICAgICAgICApICU+JSAKICAgICAgICB0aWR5cjo6c2VwYXJhdGUoc3BsaXRfcmVhZF9zdXBwb3J0LCBjKCJTUiAocmVmKSIsICJTUiAoYWx0KSIpLCAiLCIpICU+JSAKICAgICAgICBkcGx5cjo6bXV0YXRlKFNSID0gYXMuaW50ZWdlcihgU1IgKGFsdClgKSkgJT4lIAogICAgICAgIHRpZHlyOjpzZXBhcmF0ZShwYWlyZWRfc3VwcG9ydF9QUiwgYygiUFIgKHJlZikiLCAiUFIgKGFsdCkiKSwgIiwiKSAlPiUgCiAgICAgICAgZHBseXI6Om11dGF0ZShQUiA9IGFzLmludGVnZXIoYFBSIChhbHQpYCkpICU+JSAKICAgICAgICB0aWR5cjo6c2VwYXJhdGUocGFpcmVkX3N1cHBvcnRfUEUsIGMoIlBFIChyZWYpIiwgIlBFIChhbHQpIiksICIsIikgJT4lIAogICAgICAgIGRwbHlyOjptdXRhdGUoUEUgPSBhcy5pbnRlZ2VyKGBQRSAoYWx0KWApKSAlPiUgCiAgICAgICAgCiAgICAgICAgZHBseXI6OmZpbHRlcihzdnR5cGUgIT0gJ0JORCcgfCBpcy5uYShTUikgfCBQUj5TUikgJT4lICAjIHJlbW92ZSBCTkQgd2l0aCBzcGxpdCByZWFkIHN1cHBvcnQgaGlnaGVyIHRoYW4gcGFpcmVkCiAgICAgICAgdGlkeXI6OnVubmVzdChhbm5vdGF0aW9uID0gc3Ryc3BsaXQoYW5ub3RhdGlvbiwgJywnKSkgJT4lICAjIFVucGFjayBtdWx0aXBsZSBhbm5vdGF0aW9ucyBwZXIgcmVnaW9uCiAgICAgICAgdGlkeXI6OnNlcGFyYXRlKGFubm90YXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgIGMoJ0V2ZW50JywgJ0VmZmVjdCcsICdHZW5lcycsICdUcmFuc2NyaXB0JywgJ0RldGFpbCcsICdUaWVyJyksCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICdcXHwnLCBjb252ZXJ0ID0gVFJVRSkgJT4lICAjIFVucGFjayBhbm5vdGF0aW9uIGNvbHVtbnMKICAgICAgICBkcGx5cjo6bXV0YXRlKHN0YXJ0ID0gZm9ybWF0KHN0YXJ0LCBiaWcubWFyayA9ICcsJywgdHJpbSA9IFQpLAogICAgICAgICAgICAgICAgICAgICAgZW5kID0gZm9ybWF0KGVuZCwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUKSkgJT4lIAogICAgICAgIGRwbHlyOjptdXRhdGUobG9jYXRpb24gPSBzdHJfYyhjaHJvbSwgJzonLCBzdGFydCwgc2VwID0gJycpLAogICAgICAgICAgICAgICAgICAgICAgbG9jYXRpb24gPSBpZmVsc2UoaXMubmEoZW5kKSwgbG9jYXRpb24sIHN0cl9jKGxvY2F0aW9uKSkpICU+JSAKICAgICAgICBkcGx5cjo6YXJyYW5nZShUaWVyLCBFZmZlY3QsIGRlc2MoQUZfUFVSUExFKSwgR2VuZXMpICU+JSAKICAgICAgICBkcGx5cjo6bXV0YXRlKEdlbmUgPSBzdWJzZXRfZ2VuZXMoR2VuZXMsIGMoMSwgMikpLAogICAgICAgICAgICAgICAgICAgICAgR2VuZSA9IGlmZWxzZSgoc3RyX3NwbGl0KEdlbmVzLCAnJicpICU+JSBtYXBfaW50KGxlbmd0aCkpID4gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2MoR2VuZSwgJy4uLicsIHNlcCA9ICcsICcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5lKSwKICAgICAgICAgICAgICAgICAgICAgIGBPdGhlciBhZmZlY3RlZCBnZW5lc2AgPSBzdWJzZXRfZ2VuZXMoR2VuZXMsIC1jKDEsMikpICU+JSBzdHJfcmVwbGFjZV9hbGwoJyYnLCAnLCAnKSwKICAgICAgICAgICAgICAgICAgICAgIEdlbmUgPSBpZmVsc2Uoc3RyX2RldGVjdChFZmZlY3QsICJnZW5lX2Z1c2lvbiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5lICU+JSBzdHJfcmVwbGFjZV9hbGwoJyYnLCAnLCAnKSkKICAgICAgICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgIHNlcGFyYXRlKEVmZmVjdCwgYygiRWZmZWN0IiwgIk90aGVyIGVmZmVjdHMiKSwgc2VwID0gJyYnKSAlPiUgCiAgICAgICAgZHBseXI6OnNlbGVjdChUaWVyID0gdGllciwgRXZlbnQgPSBzdnR5cGUsIEdlbmUsIEVmZmVjdCA9IEVmZmVjdCwgRGV0YWlsID0gRGV0YWlsLCBMb2NhdGlvbiA9IGxvY2F0aW9uLCBBRiA9IEFGX1BVUlBMRSwgYENOIGNoZ2AgPSBDTl9jaGFuZ2VfUFVSUExFLCBTUiwgUFIsIENOID0gQ05fUFVSUExFLCBQbG9pZHkgPSBQbG9pZHlfUFVSUExFLCBQVVJQTEVfc3RhdHVzLCBgU1IgKHJlZilgLCBgUFIgKHJlZilgLCBQRSwgYFBFIChyZWYpYCwgYFNvbWF0aWMgc2NvcmVgID0gc29tYXRpY3Njb3JlLCBUcmFuc2NyaXB0ID0gVHJhbnNjcmlwdCwgYE90aGVyIGVmZmVjdHNgLCBgT3RoZXIgYWZmZWN0ZWQgZ2VuZXNgLCBgQUYgYXQgYnJlYWtwb2ludCAxYCA9IEFGX1BVUlBMRTEsIGBBRiBhdCBicmVha3BvaW50IDJgID0gQUZfUFVSUExFMiwgYENOIGF0IGJyZWFrcG9pbnQgMWAgPSBDTl9QVVJQTEUxLCBgQ04gYXQgYnJlYWtwb2ludCAyYCA9IENOX1BVUlBMRTIsIGBDTiBjaGFuZ2UgYXQgYnJlYWtwb2ludCAxYCA9IENOX2NoYW5nZV9QVVJQTEUxLCBgQ04gY2hhbmdlIGF0IGJyZWFrcG9pbnQgMmAgPSBDTl9jaGFuZ2VfUFVSUExFMiwgYEFGIGJlZm9yZSBhZGp1c3RtZW50LCBicCAxYCA9IEFGX0JQSTEsIGBBRiBiZWZvcmUgYWRqdXN0bWVudCwgYnAgMmAgPSBBRl9CUEkyCiAgICAgICAgKSAlPiUKICAgICAgICBkcGx5cjo6ZGlzdGluY3QoKQogICAgICAgICMgZHBseXI6Om11dGF0ZShjaHIgPSBmYWN0b3IoY2hyLCBsZXZlbHMgPSBjKDE6MjIsICJYIiwgIlkiLCAiTVQiKSkpICU+JQogICAgICAKICAgIH0gZWxzZSB7CiAgICAgICAgIHN2X2FsbCA8LSByZWFkcjo6cmVhZF90c3Yoc3ZfZmlsZSwgY29sX25hbWVzID0gVFJVRSkgJT4lCiAgICAgICAgZHBseXI6OnNlbGVjdCgtY2FsbGVyLCAtc2FtcGxlKSAlPiUgCiAgICAgICAgc3BsaXRfc3ZfZmllbGQoQlBJX0FGLCBpc19wY3QgPSBUKSAlPiUgCiAgICAgICAgc3BsaXRfc3ZfZmllbGQoQUYsIGlzX3BjdCA9IFQpICU+JSAKICAgICAgICBzcGxpdF9zdl9maWVsZChDTikgJT4lIAogICAgICAgIHNwbGl0X3N2X2ZpZWxkKENOX2NoYW5nZSkgJT4lIAogICAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgICBQbG9pZHkgPSBhcy5kb3VibGUoUGxvaWR5KSwKICAgICAgICAgIFBsb2lkeSA9IGZvcm1hdChQbG9pZHksIG5zbWFsbCA9IDIpCiAgICAgICAgKSAlPiUgCiAgICAgICAgdGlkeXI6OnNlcGFyYXRlKHNwbGl0X3JlYWRfc3VwcG9ydCwgYygiU1IgKHJlZikiLCAiU1IgKGFsdCkiKSwgIiwiKSAlPiUgCiAgICAgICAgZHBseXI6Om11dGF0ZShTUiA9IGFzLmludGVnZXIoYFNSIChhbHQpYCkpICU+JSAKICAgICAgICB0aWR5cjo6c2VwYXJhdGUocGFpcmVkX3N1cHBvcnRfUFIsIGMoIlBSIChyZWYpIiwgIlBSIChhbHQpIiksICIsIikgJT4lIAogICAgICAgIGRwbHlyOjptdXRhdGUoUFIgPSBhcy5pbnRlZ2VyKGBQUiAoYWx0KWApKSAlPiUgCiAgICAgICAgdGlkeXI6OnNlcGFyYXRlKHBhaXJlZF9zdXBwb3J0X1BFLCBjKCJQRSAocmVmKSIsICJQRSAoYWx0KSIpLCAiLCIpICU+JSAKICAgICAgICBkcGx5cjo6bXV0YXRlKFBFID0gYXMuaW50ZWdlcihgUEUgKGFsdClgKSkgJT4lIAogICAgICAgIAogICAgICAgIGRwbHlyOjpmaWx0ZXIoc3Z0eXBlICE9ICdCTkQnIHwgaXMubmEoU1IpIHwgUFI+U1IpICU+JSAgIyByZW1vdmUgQk5EIHdpdGggc3BsaXQgcmVhZCBzdXBwb3J0IGhpZ2hlciB0aGFuIHBhaXJlZAogICAgICAgIHRpZHlyOjp1bm5lc3QoYW5ub3RhdGlvbiA9IHN0cnNwbGl0KGFubm90YXRpb24sICcsJykpICU+JSAgIyBVbnBhY2sgbXVsdGlwbGUgYW5ub3RhdGlvbnMgcGVyIHJlZ2lvbgogICAgICAgIHRpZHlyOjpzZXBhcmF0ZShhbm5vdGF0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICBjKCdFdmVudCcsICdFZmZlY3QnLCAnR2VuZXMnLCAnVHJhbnNjcmlwdCcsICdEZXRhaWwnLCAnVGllcicpLAogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAnXFx8JywgY29udmVydCA9IFRSVUUpICU+JSAgIyBVbnBhY2sgYW5ub3RhdGlvbiBjb2x1bW5zCiAgICAgICAgZHBseXI6Om11dGF0ZShzdGFydCA9IGZvcm1hdChzdGFydCwgYmlnLm1hcmsgPSAnLCcsIHRyaW0gPSBUKSwKICAgICAgICAgICAgICAgICAgICAgIGVuZCA9IGZvcm1hdChlbmQsIGJpZy5tYXJrID0gJywnLCB0cmltID0gVCkpICU+JSAKICAgICAgICBkcGx5cjo6bXV0YXRlKGxvY2F0aW9uID0gc3RyX2MoY2hyb20sICc6Jywgc3RhcnQsIHNlcCA9ICcnKSwKICAgICAgICAgICAgICAgICAgICAgIGxvY2F0aW9uID0gaWZlbHNlKGlzLm5hKGVuZCksIGxvY2F0aW9uLCBzdHJfYyhsb2NhdGlvbikpKSAlPiUgCiAgICAgICAgZHBseXI6OmFycmFuZ2UoVGllciwgRWZmZWN0LCBkZXNjKEFGKSwgR2VuZXMpICU+JSAKICAgICAgICBkcGx5cjo6bXV0YXRlKEdlbmUgPSBzdWJzZXRfZ2VuZXMoR2VuZXMsIGMoMSwgMikpLAogICAgICAgICAgICAgICAgICAgICAgR2VuZSA9IGlmZWxzZSgoc3RyX3NwbGl0KEdlbmVzLCAnJicpICU+JSBtYXBfaW50KGxlbmd0aCkpID4gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2MoR2VuZSwgJy4uLicsIHNlcCA9ICcsICcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5lKSwKICAgICAgICAgICAgICAgICAgICAgIGBPdGhlciBhZmZlY3RlZCBnZW5lc2AgPSBzdWJzZXRfZ2VuZXMoR2VuZXMsIC1jKDEsMikpICU+JSBzdHJfcmVwbGFjZV9hbGwoJyYnLCAnLCAnKSwKICAgICAgICAgICAgICAgICAgICAgIEdlbmUgPSBpZmVsc2Uoc3RyX2RldGVjdChFZmZlY3QsICJnZW5lX2Z1c2lvbiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHZW5lICU+JSBzdHJfcmVwbGFjZV9hbGwoJyYnLCAnLCAnKSkKICAgICAgICAgICAgICAgICAgICAgICkgJT4lIAogICAgICAgIHNlcGFyYXRlKEVmZmVjdCwgYygiRWZmZWN0IiwgIk90aGVyIGVmZmVjdHMiKSwgc2VwID0gJyYnKSAlPiUgCiAgICAgICAgZHBseXI6OnNlbGVjdChUaWVyID0gdGllciwgRXZlbnQgPSBzdnR5cGUsIEdlbmUsIEVmZmVjdCA9IEVmZmVjdCwgRGV0YWlsID0gRGV0YWlsLCBMb2NhdGlvbiA9IGxvY2F0aW9uLCBBRiwgYENOIGNoZ2AgPSBDTl9jaGFuZ2UsIFNSLCBQUiwgQ04sIFBsb2lkeSwgUFVSUExFX3N0YXR1cywgYFNSIChyZWYpYCwgYFBSIChyZWYpYCwgUEUsIGBQRSAocmVmKWAsIGBTb21hdGljIHNjb3JlYCA9IHNvbWF0aWNzY29yZSwgVHJhbnNjcmlwdCA9IFRyYW5zY3JpcHQsIGBPdGhlciBlZmZlY3RzYCwgYE90aGVyIGFmZmVjdGVkIGdlbmVzYCwgYEFGIGF0IGJyZWFrcG9pbnQgMWAgPSBBRjEsIGBBRiBhdCBicmVha3BvaW50IDJgID0gQUYyLCBgQ04gYXQgYnJlYWtwb2ludCAxYCA9IENOMSwgYENOIGF0IGJyZWFrcG9pbnQgMmAgPSBDTjIsIGBDTiBjaGFuZ2UgYXQgYnJlYWtwb2ludCAxYCA9IENOX2NoYW5nZTEsIGBDTiBjaGFuZ2UgYXQgYnJlYWtwb2ludCAyYCA9IENOX2NoYW5nZTIsIGBBRiBiZWZvcmUgYWRqdXN0bWVudCwgYnAgMWAgPSBCUElfQUYxLCBgQUYgYmVmb3JlIGFkanVzdG1lbnQsIGJwIDJgID0gQlBJX0FGMgogICAgICAgICkgJT4lCiAgICAgICAgZHBseXI6OmRpc3RpbmN0KCkKICAgICAgICAjIGRwbHlyOjptdXRhdGUoY2hyID0gZmFjdG9yKGNociwgbGV2ZWxzID0gYygxOjIyLCAiWCIsICJZIiwgIk1UIikpKSAlPiUKICAgIH0KICB9IGVsc2UgewogICAgd2FybmluZygnTm8gcHJpb3JpdGl6ZWQgZXZlbnRzIGRldGVjdGVkJykKICB9CiAgcmV0dXJuKCBzdl9hbGwgKQp9CgojIyMjIyBGdW5jdGlvbiB1c2VkIGluIHRoZSAic3ZfcHJpb3JpdGl6ZSIgZnVuY3Rpb24Kc3Vic2V0X2dlbmVzID0gZnVuY3Rpb24oZ2VuZXMsIGluZCkgewogIGdlbmVzICU+JSBzdHJfc3BsaXQoJyYnKSAlPiUgbWFwKH4gLltpbmRdICU+JSByZXBsYWNlKCIiLCBOQSkgJT4lIC5bIWlzLm5hKC4pXSkgJT4lIG1hcF9jaHIofiBpZmVsc2UobGVuZ3RoKC4pID4gMCwgc3RyX2MoLiwgY29sbGFwc2UgPSAnJicpLCAiIikpCn0KCiMjIyMjIEZ1bmN0aW9uIHVzZWQgaW4gdGhlICJzdl9wcmlvcml0aXplIiBmdW5jdGlvbgpmb3JtYXRfdmFsID0gZnVuY3Rpb24odmFsLCBpc19wY3QgPSBGKSB7CiAgaWZlbHNlKCFpcy5uYSh2YWwpLCAKICAgICAgICAgZm9ybWF0KHZhbCwgIGRpZ2l0cyA9IDEpICU+JSBzdHJfYyhpZmVsc2UoaXNfcGN0LCAiJSIsICIiKSksIE5BKQp9CgojIyMjIyBGdW5jdGlvbiB1c2VkIGluIHRoZSAic3ZfcHJpb3JpdGl6ZSIgZnVuY3Rpb24gCnNwbGl0X3N2X2ZpZWxkID0gZnVuY3Rpb24oLmRhdGEsIGZpZWxkLCBpc19wY3QgPSBGKSB7CiAgZl9xID0gcmxhbmc6OmVucXVvKGZpZWxkKQogIGZfc3RyID0gcmxhbmc6OnF1b19uYW1lKGZfcSkKICBmMV9zdHIgPSBzdHJfYyhmX3N0ciwgJzEnKQogIGYyX3N0ciA9IHN0cl9jKGZfc3RyLCAnMicpCiAgZjFfcSA9IHN5bShmMV9zdHIpCiAgZjJfcSA9IHN5bShmMl9zdHIpCiAgLmRhdGEgJT4lIAogICAgc2VwYXJhdGUoISFmX3EsIGMoZjFfc3RyLCBmMl9zdHIpLCAiLCIpICU+JSAKICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICEhZjFfcSA6PSBhcy5kb3VibGUoISFmMV9xKSAqIGlmZWxzZShpc19wY3QsIDEwMCwgMSksCiAgICAgICEhZjJfcSA6PSBhcy5kb3VibGUoISFmMl9xKSAqIGlmZWxzZShpc19wY3QsIDEwMCwgMSksCiAgICAgICEhZl9xICA6PSAoISFmMV9xICsgaWZlbHNlKGlzLm5hKCEhZjJfcSksICEhZjFfcSwgISFmMl9xKSkgLyAyLAogICAgICAhIWZfcSAgOj0gZm9ybWF0X3ZhbCghIWZfcSwgaXNfcGN0KSwKICAgICAgISFmMV9xIDo9IGZvcm1hdF92YWwoISFmMV9xLCBpc19wY3QpLAogICAgICAhIWYyX3EgOj0gZm9ybWF0X3ZhbCghIWYyX3EsIGlzX3BjdCkKICAgICkKfQoKQ2FwU3RyIDwtIGZ1bmN0aW9uKHkpIHsKICBjIDwtIHN0cnNwbGl0KHksICIgIilbWzFdXQogIHBhc3RlKHRvdXBwZXIoc3Vic3RyaW5nKGMsIDEsMSkpLCBzdWJzdHJpbmcoYywgMiksCiAgICAgIHNlcD0iIiwgY29sbGFwc2U9IiAiKQp9CgojIyMjIyBBIHdyYXBwZXIgdG8gc2F2ZVdpZGdldCB3aGljaCBjb21wZW5zYXRlcyBmb3IgYXJndWFibGUgQlVHIGluIHNhdmVXaWRnZXQgd2hpY2ggcmVxdWlyZXMgYGZpbGVgIHRvIGJlIGluIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkgKHNlZSBwb3N0IGh0dHBzOi8vZ2l0aHViLmNvbS9yYW1uYXRodi9odG1sd2lkZ2V0cy9pc3N1ZXMvMjk5ICkKc2F2ZVdpZGdldEZpeCA8LSBmdW5jdGlvbiAoIHdpZGdldCwgZmlsZSwgLi4uKSB7CiAgd2Q8LWdldHdkKCkKICBvbi5leGl0KHNldHdkKHdkKSkKICBvdXREaXI8LWRpcm5hbWUoZmlsZSkKICBmaWxlPC1iYXNlbmFtZShmaWxlKQogIHNldHdkKG91dERpcik7CiAgaHRtbHdpZGdldHM6OnNhdmVXaWRnZXQod2lkZ2V0LGZpbGU9ZmlsZSwuLi4pCn0KCiMjIyMjIERlZmluZSBmdW5jdGlvbiBmb3IgZ2VuZXJhdGluZyBzcGlkZXIgd2ViIHBsb3RzIHRvIHByZXNlbnQgaW1tdW5vZ3JhbSBnZW5lcyAoY29kZSBmcm9tIGh0dHA6Ly93d3cuc3RhdGlzdGljc3RvcHJvdmVhbnl0aGluZy5jb20vMjAxMy8xMS9zcGlkZXItd2ViLXBsb3RzLWluLXIuaHRtbCkKIyBkYXRhIC0gZGF0YS5mcmFtZSBvciBtYXRyaXgKIyBkYXRhLnJvdyAtIHJvdyBvZiBkYXRhIHRvIHBsb3QgKGlmIE5VTEwgdXNlcyByb3cgMSkKIyB5LmNvbHMgLSBjb2x1bW5zIG9mIGludGVyZXN0IChpZiBOVUxMIGl0IHNlbGVjdHMgYWxsIG51bWVyaWMgY29sdW1ucykKIyBtYWluIC0gdGl0bGUgb2YgcGxvdCAoaWYgTlVMTCB0aGVuIHJvd25hbWUgb2YgZGF0YSkKIyBhZGQgLSB3aGV0aGVyIHRoZSBwbG90IHNob3VsZCBiZSBhZGRlZCB0byBhbiBleGlzdGluZyBwbG90CiMgY29sIC0gY29sb3Igb2YgdGhlIGRhdGEgbGluZQojIGx0eSAtIGx0eSBvZiB0aGUgZGF0YSBsaW5lCgp3ZWJwbG90ID0gZnVuY3Rpb24oZGF0YSwgZGF0YS5yb3cgPSBOVUxMLCB5LmNvbHMgPSBOVUxMLCBtYWluID0gTlVMTCwgYWRkID0gRiwgCiAgICBjb2wgPSAicmVkIiwgbHR5ID0gMSwgc2NhbGUgPSBUKSB7CiAgICBpZiAoIWlzLm1hdHJpeChkYXRhKSAmICFpcy5kYXRhLmZyYW1lKGRhdGEpKSAKICAgICAgICBzdG9wKCJSZXF1aXJlcyBtYXRyaXggb3IgZGF0YS5mcmFtZSIpCiAgICBpZiAoaXMubnVsbCh5LmNvbHMpKSAKICAgICAgICB5LmNvbHMgPSBjb2xuYW1lcyhkYXRhKVtzYXBwbHkoZGF0YSwgaXMubnVtZXJpYyldCiAgICBpZiAoc3VtKCFzYXBwbHkoZGF0YVssIHkuY29sc10sIGlzLm51bWVyaWMpKSA+IDApIHsKICAgICAgICBvdXQgPSBwYXN0ZTAoIlwiIiwgY29sbmFtZXMoZGF0YSlbIXNhcHBseShkYXRhLCBpcy5udW1lcmljKV0sICJcIiIsIAogICAgICAgICAgICBjb2xsYXBzZSA9ICIsICIpCiAgICAgICAgc3RvcChwYXN0ZTAoIkFsbCB5LmNvbHMgbXVzdCBiZSBudW1lcmljXG4iLCBvdXQsICIgYXJlIG5vdCBudW1lcmljIikpCiAgICB9CiAgICBpZiAoaXMubnVsbChkYXRhLnJvdykpIAogICAgICAgIGRhdGEucm93ID0gMQogICAgaWYgKGlzLmNoYXJhY3RlcihkYXRhLnJvdykpIAogICAgICAgIGlmIChkYXRhLnJvdyAlaW4lIHJvd25hbWVzKGRhdGEpKSB7CiAgICAgICAgICAgIGRhdGEucm93ID0gd2hpY2gocm93bmFtZXMoZGF0YSkgPT0gZGF0YS5yb3cpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgc3RvcCgiSW52YWxpZCB2YWx1ZSBmb3IgZGF0YS5yb3c6XG5NdXN0IGJlIGEgdmFsaWQgcm93bmFtZXMoZGF0YSkgb3Igcm93LWluZGV4IHZhbHVlIikKICAgICAgICB9CiAgICBpZiAoaXMubnVsbChtYWluKSkgCiAgICAgICAgbWFpbiA9IHJvd25hbWVzKGRhdGEpW2RhdGEucm93XQogICAgaWYgKHNjYWxlID09IFQpIHsKICAgICAgICBkYXRhID0gc2NhbGUoZGF0YVssIHkuY29sc10pCiAgICAgICAgZGF0YSA9IGFwcGx5KGRhdGEsIDIsIGZ1bmN0aW9uKHgpIHgvbWF4KGFicyh4KSkpCiAgICB9CiAgICBkYXRhID0gYXMuZGF0YS5mcmFtZShkYXRhKQogICAgbi55ID0gbGVuZ3RoKHkuY29scykKICAgIG1pbi5yYWQgPSAzNjAvbi55CiAgICBwb2xhci52YWxzID0gKDkwICsgc2VxKDAsIDM2MCwgbGVuZ3RoLm91dCA9IG4ueSArIDEpKSAqIHBpLzE4MAoKICAgIGlmIChhZGQgPT0gRikgewogICAgICAgIHBsb3QoMCwgeGxpbSA9IGMoLTIuMiwgMi4yKSwgeWxpbSA9IGMoLTIuMiwgMi4yKSwgdHlwZSA9ICJuIiwgYXhlcyA9IEYsIAogICAgICAgICAgICB4bGFiID0gIiIsIHlsYWIgPSAiIikKICAgICAgICB0aXRsZShtYWluKQogICAgICAgIGxhcHBseShwb2xhci52YWxzLCBmdW5jdGlvbih4KSBsaW5lcyhjKDAsIDIgKiBjb3MoeCkpLCBjKDAsIDIgKiBzaW4oeCkpKSkKICAgICAgICBsYXBwbHkoMTpuLnksIGZ1bmN0aW9uKHgpIHRleHQoMi4xNSAqIGNvcyhwb2xhci52YWxzW3hdKSwgMi4xNSAqIHNpbihwb2xhci52YWxzW3hdKSwgCiAgICAgICAgICAgIHkuY29sc1t4XSwgY2V4ID0gMC44KSkKCiAgICAgICAgbGFwcGx5KHNlcSgwLjUsIDIsIDAuNSksIGZ1bmN0aW9uKHgpIGxpbmVzKHggKiBjb3Moc2VxKDAsIDIgKiBwaSwgbGVuZ3RoLm91dCA9IDEwMCkpLCAKICAgICAgICAgICAgeCAqIHNpbihzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gMTAwKSksIGx3ZCA9IDAuNSwgbHR5ID0gMiwgY29sID0gImdyYXk2MCIpKQogICAgICAgIGxpbmVzKGNvcyhzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gMTAwKSksIHNpbihzZXEoMCwgMiAqIHBpLCBsZW5ndGgub3V0ID0gMTAwKSksIAogICAgICAgICAgICBsd2QgPSAxLjIsIGNvbCA9ICJncmF5NTAiKQogICAgfQoKICAgIHIgPSAxICsgZGF0YVtkYXRhLnJvdywgeS5jb2xzXQogICAgeHMgPSByICogY29zKHBvbGFyLnZhbHMpCiAgICB5cyA9IHIgKiBzaW4ocG9sYXIudmFscykKICAgIHhzID0gYyh4cywgeHNbMV0pCiAgICB5cyA9IGMoeXMsIHlzWzFdKQogICAgbGluZXMoeHMsIHlzLCBjb2wgPSBjb2wsIGx3ZCA9IDIsIGx0eSA9IGx0eSkKICAgIAogICAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKfQpgYGAKCmBgYHtyIHBsb3RfdGh1bWJuYWlsLCBjb21tZW50PU5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBHZW5lcmF0ZSBhIGZ1bGwtcmVzb2x1dGlvbiBwZGYgaW1hZ2UgYmVmb3JlIGdlbmVyYXRpbmcgYSBzbWFsbCBpbWFnZSBpbiB0aGUgY2h1bmsKa25pdHI6OmtuaXRfaG9va3Mkc2V0KHBsb3QgPSBhbGxvd190aHVtYm5haWxzKQpgYGAKCmBgYHtyIGxvYWRfbGlicmFyaWVzLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBMb2FkIGxpYnJhcmllcwpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZWRnZVIpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkobGltbWEpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoRURBU2VxKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHByZXByb2Nlc3NDb3JlKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHJhcHBvcnRvb2xzKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHR4aW1wb3J0KSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHJoZGY1KSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KG9wZW54bHN4KSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHJlYWRyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHRpZHl2ZXJzZSkpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShkcGx5cikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeSh0aWR5cikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShybGFuZykpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShEVCkpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShtYXRyaXhTdGF0cykpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeSh0aWJibGUpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoa25pdHIpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoc2NhbGVzKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KFJDaXJjb3MpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZ2dwbG90MikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShnZ2ZvcmNlKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBkZnRvb2xzKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBuZykpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShodG1sdG9vbHMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoaHRtbHdpZGdldHMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoZGV2dG9vbHMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkobGFyZXMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGFja2FnZT1wYXN0ZTAoIkVuc0RiLkhzYXBpZW5zLnYiLCBwYXJhbXMkZW5zZW1ibF92ZXJzaW9uKSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBhY2thZ2U9cGFzdGUwKCJCU2dlbm9tZS5Ic2FwaWVucy5VQ1NDLmhnIiwgcGFyYW1zJHVjc2NfZ2Vub21lX2Fzc2VtYmx5KSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkKYGBgCgpgYGB7ciBwcmVwYXJlX3BhcmFtZXRlcnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIERlZmluZSBaLXRyYW5zZm9ybWF0aW9uIGRpcmVjdGlvbgppZiAodG9sb3dlcihwYXJhbXMkc2NhbGluZykgPT0gImdlbmUtd2lzZSIpewogIHNjYWxpbmcgPC0gImdlbmUtd2lzZSIKfSBlbHNlIHsKICBzY2FsaW5nIDwtICJncm91cC13aXNlIgp9CmBgYAoKYGBge3IgdHgyZW5zZW1ibCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBBbm5vdGF0ZSB0cmFuc2NyaXB0cyB3aXRoIGdlbmUgSURzCmVkYiA8LSBldmFsKHBhcnNlKHRleHQgPSBwYXN0ZTAoIkVuc0RiLkhzYXBpZW5zLnYiLCBwYXJhbXMkZW5zZW1ibF92ZXJzaW9uKSkpCiAgCiMjIyMjIEdldCBrZXl0eXBlcyBmb3IgZ2VuZSBTWU1CT0wKa2V5cyA8LSBrZXlzKGVkYiwga2V5dHlwZT0iR0VORUlEIikKICAKIyMjIyMgR2V0IGdlbmVzIGdlbm9taWMgY29vcmRpYW50ZXMKdHgyZW5zZW1ibCA8LSBlbnNlbWJsZGI6OnNlbGVjdChlZGIsIGtleXM9a2V5cywgY29sdW1ucz1jKCJUWElEIiwgIkdFTkVJRCIpLCBrZXl0eXBlPSJHRU5FSUQiKQpuYW1lcyh0eDJlbnNlbWJsKSA8LSBnc3ViKCJUWElEIiwgInR4X25hbWUiLCBuYW1lcyh0eDJlbnNlbWJsKSkKbmFtZXModHgyZW5zZW1ibCkgPC0gZ3N1YigiR0VORUlEIiwgImdlbmVfaWQiLCBuYW1lcyh0eDJlbnNlbWJsKSkKICAKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGVkYiwga2V5cykKYGBgCgpgYGB7ciBsb2FkX3JlZl9kYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBMb2FkIHJlZmVyZW5jZSBkYXRhc2V0cwojIyMjIyBEZWZpbmUgdGhlIHJlZmVyZW5jZSBkYXRhc2V0cyBiYXNlZCBvbiB1c2VyLWRlZmluZWQgaW5wdXQKZGF0YXNldCA8LSB0b3VwcGVyKHBhcmFtcyRkYXRhc2V0KQoKcmVmX2RhdGFzZXQgPC0gbGlzdCggImV4dF9yZWYiID0gYyhwYXN0ZTAocGFyYW1zJHJlZl9kYXRhX2RpciwgIi9yZWZfZGF0YS9UQ0dBXyIsIHN0cnNwbGl0KGRhdGFzZXQsIHNwbGl0PSctJywgZml4ZWQ9VFJVRSlbWzFdXVsxXSwgIl9Db3VudHMuZXhwLmd6IiksIHBhc3RlMChwYXJhbXMkcmVmX2RhdGFfZGlyLCAiL3JlZl9kYXRhL1RDR0FfIiwgZGF0YXNldCwgIl9UYXJnZXQudHh0IiksIHBhc3RlMChzdHJzcGxpdChkYXRhc2V0LCBzcGxpdD0nLScsIGZpeGVkPVRSVUUpW1sxXV1bMV0sICIgKFRDR0EpIikpLAogICAgICAgICAgICAgICAgICAgICAiaW50X3JlZiIgPSBjKHBhc3RlMChwYXJhbXMkcmVmX2RhdGFfZGlyLCAiL3JlZl9kYXRhL1VNQ0NSX1BEQUNfQ291bnRzLmV4cC5neiIpLCBwYXN0ZTAocGFyYW1zJHJlZl9kYXRhX2RpciwgIi9yZWZfZGF0YS9VTUNDUl9QREFDX1RhcmdldC50eHQiKSwgIlBBQUQgKFVNQ0NSKSIpCikKCiMjIyMjIENyZWF0ZSBhIGxpc3Qgd2l0aCByZWZlcmVuY2UgZGF0YXNldHMKcmVmX2RhdGFzZXQubGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgoZGF0YXNldCkpCm5hbWVzKHJlZl9kYXRhc2V0Lmxpc3QpIDwtIGRhdGFzZXQKCiMjIyMjIENyZWF0ZSBhIGxpc3Qgd2l0aCB2YXJpb3VzIHNldHMgb2YgZ2VuZXMKcmVmX2dlbmVzIDwtIGMoImdlbmVzX2NhbmNlciIsICJnZW5lc19vbmNva2IiLCAiZ2VuZXNfaW1tdW5lIiwgImdlbmVzX2hyZCIpCnJlZl9nZW5lcy5saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aChyZWZfZ2VuZXMpKQpuYW1lcyhyZWZfZ2VuZXMubGlzdCkgPC0gcmVmX2dlbmVzCgojIyMjIyBDcmVhdGUgYSBsaXN0IHdpdGggY2FuY2VyIGdlbmVzIGFubm90YXRpb25zCmNhbmVyX2dlbmVzX2Fubm90IDwtIGMoIm9uY29rYl9jbGluX3ZhcnMiLCAib25jb2tiX2FsbF92YXJzIikKY2FuZXJfZ2VuZXNfYW5ub3QubGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgoY2FuZXJfZ2VuZXNfYW5ub3QpKQpuYW1lcyhjYW5lcl9nZW5lc19hbm5vdC5saXN0KSA8LSBjYW5lcl9nZW5lc19hbm5vdAoKIyMjIyMgR2V0IHRoZSBzdWJqZWN0IElECmlmICggIWlzLm5hKHBhcmFtcyRzdWJqZWN0X2lkKSApIHsKICBzdWJqZWN0SUQgPC0gcGFyYW1zJHN1YmplY3RfaWQKfSBlbHNlIHsKICBzdWJqZWN0SUQgPC0gIiIKfQoKaWYgKCAhaXMubnVsbChwYXJhbXMkYmNiaW9fcm5hc2VxKSApIHsKICAKICAjIyMjIyBHZXQgcGF0aWVudCBkYXRhIGRpciBhbmQgc2FtcGxlIGZpbGUgbmFtZQogIGRhdGFEaXIgPC0gcGFyYW1zJGJjYmlvX3JuYXNlcQogIAogICMjIyMjIExvb2sgYXQgY291bnRzRnJvbUFidW5kYW5jZSBwYXJhbWV0ZXIgdG8gY2hhbmdlIHRoZSBtZXRob2QgdG8gZ2VuZXJhdGUgdGhlIGNvdW50cwogIHR4aS5rYWxsaXN0byA8LSB0eGltcG9ydChwYXN0ZTAoZGF0YURpciwgIi9rYWxsaXN0by9hYnVuZGFuY2UudHN2IiksIHR5cGUgPSAia2FsbGlzdG8iLCB0eDJnZW5lID0gdHgyZW5zZW1ibCkKICAKICAjIyMjIyBFeHRyYWN0IGthbGxpc3RvIGNvdW50cyB0byBwcmVwYXJlIGRhdGFmcmFtZQogIGNvdW50cyA8LSBhcy5kYXRhLmZyYW1lKHR4aS5rYWxsaXN0byRjb3VudHMpICU+JQogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oKSAlPiUKICAgIGRwbHlyOjpyZW5hbWUoY291bnQgPSBWMSkKICAKfSBlbHNlIGlmICggIWlzLm51bGwocGFyYW1zJGRyYWdlbl9ybmFzZXEpICkgewogIAogICMjIyMjIEdldCBwYXRpZW50IGRhdGEgZGlyIGFuZCBzYW1wbGUgZmlsZSBuYW1lCiAgZGF0YURpciA8LSBwYXN0ZShwYXJhbXMkZHJhZ2VuX3JuYXNlcSwgImRyYWdlbiIsIHNlcCA9ICIvIikKICAKICAjIyMjIyBMb29rIGF0IGNvdW50c0Zyb21BYnVuZGFuY2UgcGFyYW1ldGVyIHRvIGNoYW5nZSB0aGUgbWV0aG9kIHRvIGdlbmVyYXRlIHRoZSBjb3VudHMKICB0eGkuc2FsbW9uIDwtIHR4aW1wb3J0KHBhc3RlMChkYXRhRGlyLCAiLyIsIGxpc3QuZmlsZXMoZGF0YURpciwgcGF0dGVybj0iXFwuc2YkIikpLCB0eXBlID0gInNhbG1vbiIsIHR4MmdlbmUgPSB0eDJlbnNlbWJsKQogIAogICMjIyMjIEV4dHJhY3Qgc2FsbW9uIGNvdW50cyB0byBwcmVwYXJlIGRhdGFmcmFtZQogIGNvdW50cyA8LSBhcy5kYXRhLmZyYW1lKHR4aS5zYWxtb24kY291bnRzKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCkgJT4lCiAgICBkcGx5cjo6cmVuYW1lKGNvdW50ID0gVjEpCn0KCiMjIyMjIENyZWF0ZSBkaXJlY3RvcnkgZm9yIHJlc3VsdHMKcmVzdWx0c19kaXIgPC0gcGFzdGUwKHBhcmFtcyRyZXBvcnRfZGlyLCAiLyIsIHBhcmFtcyRzYW1wbGVfbmFtZSwgcGFyYW1zJGRhdGFzZXRfbmFtZV9pbmNsLCAiLnJlc3VsdHMiKQoKaWYgKCAhZmlsZS5leGlzdHMocmVzdWx0c19kaXIpICkgewogIGRpci5jcmVhdGUocmVzdWx0c19kaXIsIHJlY3Vyc2l2ZT1UUlVFKQp9CgojIyMjIyBDaGVjayBpZiBzcHJlYWRzaGVldCB3aXRoIGNsaW5pY2FsIGluZm9ybWF0aW9uIGV4aXN0cwpjbGluaWNhbF9pbmZvX2ZpbGUgPC0gcGFyYW1zJGNsaW5pY2FsX2luZm8KcnVuQ2xpbmljYWxDaHVuayA8LSBGQUxTRQoKaWYgKCBmaWxlLmV4aXN0cyhjbGluaWNhbF9pbmZvX2ZpbGUpICkgewogIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNsaW5pY2FsX2luZm8iXV0gPC0gcmVhZC54bHN4KHhsc3hGaWxlID0gY2xpbmljYWxfaW5mb19maWxlLCBzaGVldCA9IDEsIGNvbE5hbWVzID0gVFJVRSwgcm93TmFtZXMgPSBGQUxTRSwgZGV0ZWN0RGF0ZXMgPSBUUlVFLCBza2lwRW1wdHlSb3dzID0gVFJVRSwgc2tpcEVtcHR5Q29scyA9IFRSVUUsIGNoZWNrLm5hbWVzID0gVFJVRSkKICBydW5DbGluaWNhbENodW5rIDwtIFRSVUUKfQoKIyMjIyMgUmVhZCBpbiBzZWxlY3RlZCBnZW5lcyBsaXN0CnJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dIDwtIHJlYWQudGFibGUocGFzdGUocGFyYW1zJHJlZl9kYXRhX2RpciwgcGFyYW1zJGdlbmVzX2NhbmNlciwgc2VwPSIvIiksIHNlcD0iXHQiLCBhcy5pcz1UUlVFLCBoZWFkZXI9VFJVRSwgcm93Lm5hbWVzPU5VTEwsIHF1b3RlPSIiKQpyZWZfZ2VuZXMubGlzdFtbImdlbmVzX29uY29rYiJdXSA8LSByZWFkLnRhYmxlKHBhc3RlKHBhcmFtcyRyZWZfZGF0YV9kaXIsIHBhcmFtcyRvbmNva2JfZ2VuZXMsIHNlcD0iLyIpLCBzZXA9Ilx0IiwgYXMuaXM9VFJVRSwgaGVhZGVyPVRSVUUsIHJvdy5uYW1lcz1OVUxMLCBxdW90ZT0iIiwgY29tbWVudC5jaGFyID0gIiIpCnJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzIDwtIHJlYWQudGFibGUocGFzdGUocGFyYW1zJHJlZl9kYXRhX2RpciwgcGFyYW1zJGdlbmVzX2ltbXVuZV9tYXJrZXJzLCBzZXA9Ii8iKSwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1UUlVFLCByb3cubmFtZXM9TlVMTCwgcXVvdGU9IiIpCnJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaHJkIl1dIDwtIHJlYWQudGFibGUocGFzdGUocGFyYW1zJHJlZl9kYXRhX2RpciwgcGFyYW1zJGdlbmVzX2hyZCwgc2VwPSIvIiksIHNlcD0iXHQiLCBhcy5pcz1UUlVFLCBoZWFkZXI9VFJVRSwgcm93Lm5hbWVzPU5VTEwsIHF1b3RlPSIiKQoKaWYgKCBwYXJhbXMkaW1tdW5vZ3JhbSApIHsKICByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bm9ncmFtIDwtIHJlYWQudGFibGUocGFzdGUocGFyYW1zJHJlZl9kYXRhX2RpciwgcGFyYW1zJGdlbmVzX2ltbXVub2dyYW0sIHNlcD0iLyIpLCBzZXA9Ilx0IiwgYXMuaXM9VFJVRSwgaGVhZGVyPVRSVUUsIHJvdy5uYW1lcz1OVUxMLCBxdW90ZT0iIikKfQoKIyMjIyMgUmVhZCBpbiBnZW5lIGZ1c2lvbiBkYXRhIGZvciBpbnZlc3RpZ2F0ZSBzYW1wbGUKIyMjIyMgUmVhZCBpbiBhcnJpYmEgYW5kIHBpenpseSBmdXNpb24gY2FsbHMKIyMjIyMgQ2hlY2sgaWYgYXJyaWJhIG91dHB1dCBmaWxlIGV4aXN0cwphcnJpYmFfZmlsZSA8LSBwYXN0ZShkYXRhRGlyLCAiYXJyaWJhIiwgImZ1c2lvbnMudHN2Iiwgc2VwID0gIi8iKQphcnJpYmFfcGRmIDwtIHBhc3RlKGRhdGFEaXIsICJhcnJpYmEiLCAiZnVzaW9ucy5wZGYiLCBzZXAgPSAiLyIpCnJ1bkFycmliYUNodW5rIDwtIEZBTFNFCnJ1bkZ1c2lvbkNodW5rIDwtIEZBTFNFCgppZiAoIGZpbGUuZXhpc3RzKGFycmliYV9maWxlKSApIHsKICByZWZfZ2VuZXMubGlzdFtbImFycmliYSJdXSA8LSByZWFkLnRhYmxlKGZpbGUgPSBhcnJpYmFfZmlsZSwgaGVhZGVyID0gVFJVRSwgY29tbWVudC5jaGFyID0gIiIsIHF1b3RlID0gIiIpCiAgCiAgIyMjIyMgTWFrZSBzdXJlIHRoYXQgYXQgbGVhc3Qgb25lIGZ1c2lvbnMgaGFzIGJlZW4gcmVwb3J0ZWQgYnkgQXJyaWJhCiAgaWYgKCBucm93KHJlZl9nZW5lcy5saXN0W1siYXJyaWJhIl1dKSA+IDAgKSB7CiAgICAKICAgICMjIyMjIENvbnZlcnQgQXJyaWJhIHBkZiBib29rbGV0IHdpdGggZnVzaW9uIHBsb3RzIHRvIHBuZyBpbWFnZXMKICAgIGlmICggZmlsZS5leGlzdHMoYXJyaWJhX3BkZikgKSB7CiAgICAgIGFycmliYV9wbG90cyhhcnJpYmFfZmlsZSA9IGFycmliYV9maWxlLCBhcnJpYmFfcmVzdWx0cyA9IHJlZl9nZW5lcy5saXN0W1siYXJyaWJhIl1dLCByZXN1bHRzX2RpciA9IHBhc3RlMChyZXN1bHRzX2RpciwgIi9hcnJpYmEiKSkKICAgIH0KICAgIAogICAgIyMjIyMgV3JpdGUgbGlzdCBvZiBmdXNpb24gZXZlbnRzIGZvciB3aGljaCBBcnJpYmEgcGxvdCBpcyBhdmFpbGFibGUgaW50byBhIGZpbGUgKGZvciBQSUVkYiBwb3J0YWwpCiAgICBmdXNpb24gPC0gZ3N1YigiOiIsICIuIiwgYygiIiwgcGFzdGUwKG1ha2UubmFtZXMocGFzdGUocmVmX2dlbmVzLmxpc3RbWyJhcnJpYmEiXV0kWC5nZW5lMSwgcmVmX2dlbmVzLmxpc3RbWyJhcnJpYmEiXV0kZ2VuZTIsIHNlcCA9ICJfXyIpKSwgIl8iLCByZWZfZ2VuZXMubGlzdFtbImFycmliYSJdXSRicmVha3BvaW50MSwgIi0iLCByZWZfZ2VuZXMubGlzdFtbImFycmliYSJdXSRicmVha3BvaW50MikpKQogICAgCiAgICB3cml0ZS50YWJsZShwcmVwYXJlMndyaXRlKGZ1c2lvbiksIGZpbGUgPSBwYXN0ZTAocmVzdWx0c19kaXIsICIvIiwgcGFyYW1zJHNhbXBsZV9uYW1lLCBwYXJhbXMkZGF0YXNldF9uYW1lX2luY2wsICIuUk5Bc2VxX3JlcG9ydC5hcnJpYmFfZnVzaW9ucy50eHQiKSwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCByb3cubmFtZXM9RkFMU0UsIGNvbC5uYW1lcz1GQUxTRSwgYXBwZW5kID0gRkFMU0UgKQogIAogICAgcnVuQXJyaWJhQ2h1bmsgPC0gVFJVRQogICAgcnVuRnVzaW9uQ2h1bmsgPC0gVFJVRQogICAgCiAgfSBlbHNlIHsKICAgICMjIyMjIFdyaXRlIGxpc3Qgb2YgZnVzaW9uIGV2ZW50cyBmb3Igd2hpY2ggYXJyaWJhIHBsb3QgaXMgYXZhaWxhYmxlIGludG8gYSBmaWxlIChmb3IgUElFZGIgcG9ydGFsKQogIHdyaXRlLnRhYmxlKHByZXBhcmUyd3JpdGUoIiIpLCBmaWxlID0gcGFzdGUwKHJlc3VsdHNfZGlyLCAiLyIsIHBhcmFtcyRzYW1wbGVfbmFtZSwgcGFyYW1zJGRhdGFzZXRfbmFtZV9pbmNsLCAiLlJOQXNlcV9yZXBvcnQuYXJyaWJhX2Z1c2lvbnMudHh0IiksIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgcm93Lm5hbWVzPUZBTFNFLCBjb2wubmFtZXM9RkFMU0UsIGFwcGVuZCA9IEZBTFNFICkKICB9CiAgCn0gZWxzZSB7CiAgIyMjIyMgV3JpdGUgbGlzdCBvZiBmdXNpb24gZXZlbnRzIGZvciB3aGljaCBhcnJpYmEgcGxvdCBpcyBhdmFpbGFibGUgaW50byBhIGZpbGUgKGZvciBQSUVkYiBwb3J0YWwpCiAgd3JpdGUudGFibGUocHJlcGFyZTJ3cml0ZSgiIiksIGZpbGUgPSBwYXN0ZTAocmVzdWx0c19kaXIsICIvIiwgcGFyYW1zJHNhbXBsZV9uYW1lLCBwYXJhbXMkZGF0YXNldF9uYW1lX2luY2wsICIuUk5Bc2VxX3JlcG9ydC5hcnJpYmFfZnVzaW9ucy50eHQiKSwgc2VwPSJcdCIsIHF1b3RlPUZBTFNFLCByb3cubmFtZXM9RkFMU0UsIGNvbC5uYW1lcz1GQUxTRSwgYXBwZW5kID0gRkFMU0UgKQp9CgojIyMjIyBSZWFkIGluIGRyYWdlbiBmdXNpb24gY2FsbHMKIyMjIyMgQ2hlY2sgaWYgZHJhZ2VuIG91dHB1dCBmaWxlIGV4aXN0cwpkcmFnZW5fZnVzaW9uX2ZpbGUgPC0gcGFzdGUoZGF0YURpciwgbGlzdC5maWxlcyhkYXRhRGlyLCBwYXR0ZXJuPSJcXC5mdXNpb25fY2FuZGlkYXRlcy5maW5hbCQiKSwgc2VwID0gIi8iKQpydW5EcmFnZW5GdXNpb25DaHVuayA8LSBGQUxTRQoKaWYgKCAhaXMubnVsbChwYXJhbXMkZHJhZ2VuX3JuYXNlcSkgJiYgZmlsZS5leGlzdHMoZHJhZ2VuX2Z1c2lvbl9maWxlKSApIHsKICAKICAjIyMjIyBEcmFnZW4ncyBmdXNpb24gb3V0cHV0IGZpbGUgaGVhZGVyIHN0YXJ0cyB3aXRoICcjJyBoZW5jZSBjaGFuZ2UgdGhlIGNvbW1lbnQgaW5kaWNhdG9yIG9wdGlvbiB0byAnXicgKCBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNzE5NjQ3MC9yZWFkaW5nLWEtbGluZS10aGF0LXN0YXJ0cy13aXRoLWEtaGFzaC1vbi1hLXR4dC1maWxlICkKICAKICBkcmFnZW5fZnVzaW9uIDwtIHJlYWQudGFibGUoZmlsZSA9IGRyYWdlbl9mdXNpb25fZmlsZVsxXSwgaGVhZGVyID0gVFJVRSwgY29tbWVudC5jaGFyID0gJ14nLCBxdW90ZSA9ICIiKQogIAogICMjIyMjIENoZWNrIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgdmVyc2lvbgogICMjIyMjICBEcmFnZW4ncyBmdXNpb24gZm9ybWF0IHZlcnNpb24gMy45LjMKICBpZiAoIGFsbChjKCJYLkZ1c2lvbkdlbmUiLCAiU2NvcmUiLCAiTGVmdEJyZWFrcG9pbnQiLCAiUmlnaHRCcmVha3BvaW50IiwgIkdlbmUxTG9jYXRpb24iLCAiR2VuZTJMb2NhdGlvbiIsICJHZW5lMVNlbnNlIiwgIkdlbmUyU2Vuc2UiLCAiR2VuZTFJZCIsICJHZW5lMklkIiwgIk51bVNwbGl0UmVhZHMiLCAiTnVtU29mdENsaXBwZWRSZWFkcyIsICJOdW1QYWlyZWRSZWFkcyIsICJSZWFkTmFtZXMiKSAlaW4lIGNvbG5hbWVzKGRyYWdlbl9mdXNpb24pKSApIHsKICAgIGNvbG5hbWVzKGRyYWdlbl9mdXNpb24pIDwtIGMoIkZ1c2lvbkdlbmUiLCAiU2NvcmUiLCAiTGVmdEJyZWFrcG9pbnQiLCAiUmlnaHRCcmVha3BvaW50IiwgIkdlbmUxTG9jYXRpb24iLCAiR2VuZTJMb2NhdGlvbiIsICJHZW5lMVNlbnNlIiwgIkdlbmUyU2Vuc2UiLCAiR2VuZTFJZCIsICJHZW5lMklkIiwgIk51bVNwbGl0UmVhZHMiLCAiTnVtU29mdENsaXBwZWRSZWFkcyIsICJOdW1QYWlyZWRSZWFkcyIsICJSZWFkTmFtZXMiKQogIH0gZWxzZSBpZiAoIGFsbChjKCJYLkZ1c2lvbkdlbmUiLCAiU2NvcmUiLCAiTGVmdEJyZWFrcG9pbnQiLCAiUmlnaHRCcmVha3BvaW50IiwgIlJlYWROYW1lcyIpICVpbiUgY29sbmFtZXMoZHJhZ2VuX2Z1c2lvbikpICkgewogICAgY29sbmFtZXMoZHJhZ2VuX2Z1c2lvbikgPC0gYygiRnVzaW9uR2VuZSIsICJTY29yZSIsICJMZWZ0QnJlYWtwb2ludCIsICJSaWdodEJyZWFrcG9pbnQiLCAiUmVhZE5hbWVzIikKICB9CiAgCiAgZHJhZ2VuX2Z1c2lvbl9nZW5lcyA8LSBkcmFnZW5fZnVzaW9uICU+JQogICAgdGlkeXI6OnNlcGFyYXRlKGNvbCA9IEZ1c2lvbkdlbmUsIGludG8gPSBjKCJnZW5lMSIsICJnZW5lMiIpLCBzZXAgPSAiLS0iKQogIAogIHJlZl9nZW5lcy5saXN0W1siZHJhZ2VuRnVzaW9uIl1dIDwtIGRyYWdlbl9mdXNpb25fZ2VuZXMKICAKICBydW5EcmFnZW5GdXNpb25DaHVuayA8LSBUUlVFCiAgcnVuRnVzaW9uQ2h1bmsgPC0gVFJVRQp9CgoKIyMjIyMgUmVhZCBpbiBwaXp6bHkgZnVzaW9uIGNhbGxzCiMjIyMjIENoZWNrIGlmIHBpenpseSBvdXRwdXQgZmlsZSBleGlzdHMKcGl6emx5X2ZpbGUgPC0gcGFzdGUoZGF0YURpciwgInBpenpseSIsIHBhc3RlMChwYXJhbXMkc2FtcGxlX25hbWUsICItZmxhdC50c3YiKSwgc2VwID0gIi8iKQpwaXp6bHlfZmlsZV9maWx0ZXJlZCA8LSBwYXN0ZShkYXRhRGlyLCAicGl6emx5IiwgcGFzdGUwKHBhcmFtcyRzYW1wbGVfbmFtZSwgIi1mbGF0LWZpbHRlcmVkLnRzdiIpLCBzZXAgPSAiLyIpCnJ1blBpenpseUNodW5rIDwtIEZBTFNFCgppZiAoICFpcy5udWxsKHBhcmFtcyRiY2Jpb19ybmFzZXEpICYmICBmaWxlLmV4aXN0cyhwaXp6bHlfZmlsZSkgKSB7CiAgcmVmX2dlbmVzLmxpc3RbWyJwaXp6bHkiXV0gPC0gcmVhZC50YWJsZShmaWxlID0gcGl6emx5X2ZpbGUsIGhlYWRlciA9IFRSVUUsIHF1b3RlID0gIiIpCiAgcnVuUGl6emx5Q2h1bmsgPC0gVFJVRQogIHJ1bkZ1c2lvbkNodW5rIDwtIFRSVUUKfSBlbHNlIGlmICggZmlsZS5leGlzdHMocGl6emx5X2ZpbGVfZmlsdGVyZWQpICkgewogIHJlZl9nZW5lcy5saXN0W1sicGl6emx5Il1dIDwtIHJlYWQudGFibGUoZmlsZSA9IHBpenpseV9maWxlX2ZpbHRlcmVkLCBoZWFkZXIgPSBUUlVFLCBxdW90ZSA9ICIiKQogIHJ1blBpenpseUNodW5rIDwtIFRSVUUKICBydW5GdXNpb25DaHVuayA8LSBUUlVFCn0KCiMjIyMjIFJlYWQgaW4gbXV0YXRpb24gZGF0YSBmb3IgaW52ZXN0aWdhdGUgc2FtcGxlCiMjIyMjIEdldCB0aGUgZ2Vub21pYyBvdXRwdXQgZGF0YSBmcm9tIHVtY2NyaXNlCmlmICggIWlzLm51bGwocGFyYW1zJHVtY2NyaXNlKSApIHsKICB1bWNjcmlzZSA8LSB1bmxpc3Qoc3Ryc3BsaXQocGFyYW1zJHVtY2NyaXNlLCBzcGxpdD0nLycsIGZpeGVkPVRSVUUpKQogIHVtY2NyaXNlIDwtIHVtY2NyaXNlW2xlbmd0aCh1bWNjcmlzZSldCiAgCiAgIyMjIyMgQ2hlY2sgaWYgUENHUiAobXV0YXRpb24pIG91dHB1dCBmaWxlIGV4aXN0cwogIHJ1blBjZ3JDaHVuayA8LSBUUlVFCiAgCiAgaWYgKCBmaWxlLmV4aXN0cyhwYXN0ZShwYXJhbXMkdW1jY3Jpc2UsICJzbWFsbF92YXJpYW50cyIsIHBhc3RlMCh1bWNjcmlzZSwgIi1zb21hdGljLnBjZ3Iuc252c19pbmRlbHMudGllcnMudHN2IiksIHNlcCA9ICIvIikpICkgewogICAgcGNncl9maWxlIDwtIHBhc3RlKHBhcmFtcyR1bWNjcmlzZSwgInNtYWxsX3ZhcmlhbnRzIiwgcGFzdGUwKHVtY2NyaXNlLCAiLXNvbWF0aWMucGNnci5zbnZzX2luZGVscy50aWVycy50c3YiKSwgc2VwID0gIi8iKQogIH0gZWxzZSBpZiAoIGZpbGUuZXhpc3RzKHBhc3RlKHBhcmFtcyR1bWNjcmlzZSwgIi4uIiwgIndvcmsiLCB1bWNjcmlzZSwgInBjZ3IiLCBwYXN0ZTAodW1jY3Jpc2UsICItc29tYXRpYy5wY2dyLnNudnNfaW5kZWxzLnRpZXJzLnRzdiIpLCBzZXAgPSAiLyIpKSApIHsKICAgIHBjZ3JfZmlsZSA8LSBwYXN0ZShwYXJhbXMkdW1jY3Jpc2UsICIuLiIsICJ3b3JrIiwgdW1jY3Jpc2UsICJwY2dyIiwgcGFzdGUwKHVtY2NyaXNlLCAiLXNvbWF0aWMucGNnci5zbnZzX2luZGVscy50aWVycy50c3YiKSwgc2VwID0gIi8iKQogIH0gZWxzZSBpZiAoIGZpbGUuZXhpc3RzKHBhc3RlKHBhcmFtcyR1bWNjcmlzZSwgInBjZ3IiLCBwYXN0ZTAodW1jY3Jpc2UsICItc29tYXRpYy5wY2dyLnNudnNfaW5kZWxzLnRpZXJzLnRzdiIpLCBzZXAgPSAiLyIpKSApIHsKICAgIHBjZ3JfZmlsZSA8LSBwYXN0ZShwYXJhbXMkdW1jY3Jpc2UsICJwY2dyIiwgcGFzdGUwKHVtY2NyaXNlLCAiLXNvbWF0aWMucGNnci5zbnZzX2luZGVscy50aWVycy50c3YiKSwgc2VwID0gIi8iKQogIH0gZWxzZSBpZiAoIGZpbGUuZXhpc3RzKHBhc3RlKHBhcmFtcyR1bWNjcmlzZSwgInBjZ3IiLCBwYXN0ZTAodW1jY3Jpc2UsICItc29tYXRpYy5wY2dyX2FjbWcuZ3JjaDM3LnNudnNfaW5kZWxzLnRpZXJzLnRzdiIpLCBzZXAgPSAiLyIpKSApIHsKICAgIHBjZ3JfZmlsZSA8LSBwYXN0ZShwYXJhbXMkdW1jY3Jpc2UsICJwY2dyIiwgcGFzdGUwKHVtY2NyaXNlLCAiLXNvbWF0aWMucGNncl9hY21nLmdyY2gzNy5zbnZzX2luZGVscy50aWVycy50c3YiKSwgc2VwID0gIi8iKQogIH0gZWxzZSB7CiAgICBydW5QY2dyQ2h1bmsgPC0gRkFMU0UKICB9CiAgCiAgaWYgKCBydW5QY2dyQ2h1bmsgKSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0gPC0gcmVhZC50YWJsZShwY2dyX2ZpbGUsIHNlcD0iXHQiLCBhcy5pcz1UUlVFLCBoZWFkZXI9VFJVRSwgcm93Lm5hbWVzPU5VTEwsIGZpbGwgPSBUUlVFLCBxdW90ZSA9ICIiKQogICAgCiAgICAjIyMjIyBTaW1wbGlmeSB0aGUgdmFyaWFudHMgdHlwZXMKICAgIHJlZl9nZW5lcy5saXN0W1sicGNnciJdXSRDT05TRVFVRU5DRSA8LSBnc3ViKCJfdmFyaWFudCIsICIiLCByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kQ09OU0VRVUVOQ0UpCiAgICByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kQ09OU0VRVUVOQ0UgPC0gZ3N1YigiXyIsICIgIiwgcmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dJENPTlNFUVVFTkNFKQogICAgCiAgICAjIyMjIyBTaW1wbGlmeSB0aWVycycgYW5ub3RhdGlvbnMgYW5kIEFGcwogICAgcmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dJFRJRVIgPC0gZ3N1YigiVElFUiAiLCAiIiwgcmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dJFRJRVIpCiAgICByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kQUZfVFVNT1IgPC0gcm91bmQocmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dJEFGX1RVTU9SLCBkaWdpdHMgPSAyKQogIH0gZWxzZSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0gPC0gTlVMTAogIH0KICAKICAjIyMjIyBDaGVjayBpZiBwdXJwbGUgKENOKSBvdXRwdXQgZmlsZSBleGlzdHMKICBwdXJwbGVfZmlsZV8xIDwtIHBhc3RlKHBhcmFtcyR1bWNjcmlzZSwgInB1cnBsZSIsIHBhc3RlMCh1bWNjcmlzZSwgIi5wdXJwbGUuZ2VuZS5jbnYiKSwgc2VwID0gIi8iKQogIHB1cnBsZV9maWxlXzIgPC0gcGFzdGUocGFyYW1zJHVtY2NyaXNlLCAicHVycGxlIiwgcGFzdGUwKHVtY2NyaXNlLCAiLnB1cnBsZS5jbnYuZ2VuZS50c3YiKSwgc2VwID0gIi8iKQogIHJ1blB1cnBsZUNodW5rIDwtIFRSVUUKICAKICBpZiAoIGZpbGUuZXhpc3RzKHB1cnBsZV9maWxlXzEpICkgewogICAgcmVmX2dlbmVzLmxpc3RbWyJwdXJwbGUiXV0gPC0gcmVhZC50YWJsZShwdXJwbGVfZmlsZV8xLCBzZXA9Ilx0IiwgYXMuaXM9VFJVRSwgaGVhZGVyPVRSVUUsIHJvdy5uYW1lcz1OVUxMLCBmaWxsID0gVFJVRSwgcXVvdGUgPSAiIikKICB9IGVsc2UgaWYgKCBmaWxlLmV4aXN0cyhwdXJwbGVfZmlsZV8yKSApIHsKICAgIHJlZl9nZW5lcy5saXN0W1sicHVycGxlIl1dIDwtIHJlYWQudGFibGUocHVycGxlX2ZpbGVfMiwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1UUlVFLCByb3cubmFtZXM9TlVMTCwgZmlsbCA9IFRSVUUsIHF1b3RlID0gIiIpCiAgICBjb2xuYW1lcyhyZWZfZ2VuZXMubGlzdFtbInB1cnBsZSJdXSkgPC0gc2FwcGx5KGNvbG5hbWVzKHJlZl9nZW5lcy5saXN0W1sicHVycGxlIl1dKSwgQ2FwU3RyKQogIH0gZWxzZSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInB1cnBsZSJdXSA8LSBOVUxMCiAgICBydW5QdXJwbGVDaHVuayA8LSBGQUxTRQogIH0KICAKICAjIyMjIyBDaGVjayBpZiBtYW50YSAoc3RydWN0dXJhbCB2YXJpYW50cyAoU1ZzKSkgZmlsZSBleGlzdHMKICBzdl9maWxlXzEgPC0gcGFzdGUocGFyYW1zJHVtY2NyaXNlLCAic3RydWN0dXJhbCIsIHBhc3RlMCh1bWNjcmlzZSwgIi1zdi1wcmlvcml0aXplLW1hbnRhLXBhc3MudHN2IiksIHNlcCA9ICIvIikKICBzdl9maWxlXzIgPC0gcGFzdGUocGFyYW1zJHVtY2NyaXNlLCAic3RydWN0dXJhbCIsIHBhc3RlMCh1bWNjcmlzZSwgIi1tYW50YS50c3YiKSwgc2VwID0gIi8iKQogIHJ1blNWc0NodW5rIDwtIFRSVUUKICAKICBpZiAoIGZpbGUuZXhpc3RzKHN2X2ZpbGVfMSkgKSB7CiAgICByZWZfZ2VuZXMubGlzdFtbIm1hbnRhIl1dIDwtIHN2X3ByaW9yaXRpemVfc2hvcnQoc3ZfZmlsZV8xKQogIH0gZWxzZSBpZiAoIGZpbGUuZXhpc3RzKHN2X2ZpbGVfMikgKSB7CiAgICByZWZfZ2VuZXMubGlzdFtbIm1hbnRhIl1dIDwtIHN2X3ByaW9yaXRpemUoc3ZfZmlsZV8yKQogICAgcmVmX2dlbmVzLmxpc3RbWyJtYW50YSJdXSA8LSByZWZfZ2VuZXMubGlzdFtbIm1hbnRhIl1dWywgYygiVGllciIsICJFdmVudCIsICJHZW5lIiwgIkVmZmVjdCIsICJEZXRhaWwiLCAiTG9jYXRpb24iLCAiQUYiLCAiQ04gY2hnIiwgIlNSIiwgIlBSIiwgIkNOIiwgIlBsb2lkeSIsICJUcmFuc2NyaXB0IiwgIk90aGVyIGVmZmVjdHMiKV0KICAgIAogICAgIyMjIyMgQ2hlY2sgaWYgdGhlcmUgYXJlIGFueSBTVnMKICAgIGlmICggIWlzLm51bGwocmVmX2dlbmVzLmxpc3RbWyJtYW50YSJdXSkgKSB7CiAgICAgIAogICAgICAjIyMjIyBPbWl0IFNWcyB3aXRob3V0IGFzc2lnbmVkIGdlbmUKICAgICAgcmVmX2dlbmVzLmxpc3RbWyJtYW50YSJdXSA8LSByZWZfZ2VuZXMubGlzdFtbIm1hbnRhIl1dWyByZWZfZ2VuZXMubGlzdFtbIm1hbnRhIl1dJEdlbmUgIT0gIiIsICBdCiAgICB9IGVsc2UgewogICAgICAjIyMjIyBDcmVhdGUgZW1wdHkgZGF0YWZyYW1lCiAgICAgIHJlZl9nZW5lcy5saXN0W1sibWFudGEiXV0gPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbCA9IDE0LCBucm93ID0gMCkpCiAgICAgIGNvbG5hbWVzKHJlZl9nZW5lcy5saXN0W1sibWFudGEiXV0pIDwtIGMoIlRpZXIiLCAiRXZlbnQiLCAiR2VuZSIsICJFZmZlY3QiLCAiRGV0YWlsIiwgIkxvY2F0aW9uIiwgIkFGIiwgIkNOIGNoZyIsICJTUiIsICJQUiIsICJDTiIsICJQbG9pZHkiLCAiVHJhbnNjcmlwdCIsICJPdGhlciBlZmZlY3RzIikKICAgIH0KICAgIAogIH0gZWxzZSB7CiAgICByZWZfZ2VuZXMubGlzdFtbIm1hbnRhIl1dIDwtIE5VTEwKICAgIHJ1blNWc0NodW5rIDwtIEZBTFNFCiAgfQogIAogICMjIyMjIEV4dHJhY3Qgc3ViamVjdCBJRCAocGFydCBvZiB0aGUgdW1jY3Jpc2Ugb3V0cHV0IGZvbGRlciBuYW1lKSBhbmQgYWRkIGl0IHRvIHRoZSBNeVNRTCBpbnNlcnQgY29tbWFuZC4gVGhpcyB3aWxsIG92ZXJ3cml0ZSBhcmd1bWVudCBwYXNzZWQgdG8gIi0tY2xpbmljYWxfaWQiIGZsYWcKICBzdWJqZWN0SUQgPC0gdW5saXN0KHN0cnNwbGl0KHRhaWwodW5saXN0KHN0cnNwbGl0KHBhcmFtcyR1bWNjcmlzZSwgc3BsaXQ9Jy8nLCBmaXhlZD1UUlVFKSksIG49MSksIHNwbGl0PSdfXycsIGZpeGVkPVRSVUUpKVsxXQogIAp9IGVsc2UgewogIHJ1blBjZ3JDaHVuayA8LSBGQUxTRQogIHJ1blB1cnBsZUNodW5rIDwtIEZBTFNFCiAgcnVuU1ZzQ2h1bmsgPC0gRkFMU0UKfQoKIyMjIyMgUmVhZCBpbiBPbmNvS0IgKGh0dHA6Ly9vbmNva2Iub3JnKSBhbm5vdGF0aW9ucwpjYW5lcl9nZW5lc19hbm5vdC5saXN0W1sib25jb2tiX2NsaW5fdmFycyJdXSA8LSByZWFkLnRhYmxlKHBhc3RlKHBhcmFtcyRyZWZfZGF0YV9kaXIsIHBhcmFtcyRvbmNva2JfY2xpbl92YXJzLCBzZXA9Ii8iKSwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1UUlVFLCByb3cubmFtZXM9TlVMTCwgcXVvdGU9IiIpCmNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJvbmNva2JfYWxsX3ZhcnMiXV0gPC0gcmVhZC50YWJsZShwYXN0ZShwYXJhbXMkcmVmX2RhdGFfZGlyLCBwYXJhbXMkb25jb2tiX2FsbF92YXJzLCBzZXA9Ii8iKSwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1UUlVFLCByb3cubmFtZXM9TlVMTCwgcXVvdGU9IiIsIGZpbGwgPSBUUlVFKQoKIyMjIyMgUmVhZCBpbiBDSVZpQyAoaHR0cHM6Ly9jaXZpY2RiLm9yZy8pIGFubm90YXRpb25zCmNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY192YXJfc3VtbWFyaWVzIl1dIDwtIHJlYWQudGFibGUocGFzdGUocGFyYW1zJHJlZl9kYXRhX2RpciwgcGFyYW1zJGNpdmljX3Zhcl9zdW1tYXJpZXMsIHNlcD0iLyIpLCBzZXA9Ilx0IiwgYXMuaXM9VFJVRSwgaGVhZGVyPVRSVUUsIHJvdy5uYW1lcz1OVUxMLCBxdW90ZT0iIiwgZmlsbCA9IFRSVUUpCmNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV0gPC0gcmVhZC50YWJsZShwYXN0ZShwYXJhbXMkcmVmX2RhdGFfZGlyLCBwYXJhbXMkY2l2aWNfY2xpbl9ldmlkLCBzZXA9Ii8iKSwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1UUlVFLCByb3cubmFtZXM9TlVMTCwgcXVvdGU9IiIsIGZpbGwgPSBUUlVFKQoKIyMjIyMgUmVhZCBpbiBDYW5jZXIgQmlvbWFya2VycyBkYXRhYmFzZSAoaHR0cHM6Ly93d3cuY2FuY2VyZ2Vub21laW50ZXJwcmV0ZXIub3JnL2Jpb21hcmtlcnMpIGFubm90YXRpb25zLiBUaGlzIGlzIG1haW5seSB1c2VkIHRvIGFubm90YXRlIHJlcG9ydGVkIGZ1c2lvbiBldmVudHMKY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNhbmNlcl9iaW9tYXJrZXJzX3RyYW5zIl1dIDwtIHJlYWQudGFibGUocGFzdGUocGFyYW1zJHJlZl9kYXRhX2RpciwgcGFyYW1zJGNhbmNlcl9iaW9tYXJrZXJzX3RyYW5zLCBzZXA9Ii8iKSwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1UUlVFLCByb3cubmFtZXM9TlVMTCwgcXVvdGU9IiIsIGZpbGwgPSBUUlVFKQoKIyMjIyMgUmVhZCBpbiBGdXNpb25HREIgZGF0YWJhc2UgKGh0dHBzOi8vY2NzbS51dGguZWR1L0Z1c2lvbkdEQi8pIHVzZWQgdG8gYW5ub3RhdGUgcmVwb3J0ZWQgZnVzaW9uIGV2ZW50cywgd2l0aCBpbmZvIGFib3V0IGhlYWQgYW5kIHRhaWwgZ2VuZXMuCmNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJGdXNpb25HREIiXV0gPC0gcmVhZC50YWJsZShwYXN0ZShwYXJhbXMkcmVmX2RhdGFfZGlyLCBwYXJhbXMkRnVzaW9uR0RCLCBzZXA9Ii8iKSwgc2VwPSJcdCIsIGFzLmlzPVRSVUUsIGhlYWRlcj1GQUxTRSwgcm93Lm5hbWVzPU5VTEwsIHF1b3RlPSIiLCBmaWxsID0gVFJVRSkKbmFtZXMoY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbIkZ1c2lvbkdEQiJdXSkgPC0gYygiSGdlbmUiLCAiSGdlbmVJRCIsICJUZ2VuZSIsICJUZ2VuZUlEIiwgIkZHbmFtZSIsICJGR0lEIikKCgojIyMjIyBBZGQgcmVmZW5lbmNlIGNvaG9ydCBuYW1lIHRvIHRoZSBzYW1wbGUgbmFtZQppZiAoIHBhcmFtcyRkYXRhc2V0X25hbWVfaW5jbCAhPSAiIiApIHsKICBzYW1wbGVfbmFtZSA8LSBwYXN0ZTAocGFyYW1zJHNhbXBsZV9uYW1lLCAiXyIsIHBhcmFtcyRkYXRhc2V0KQp9IGVsc2UgewogIHNhbXBsZV9uYW1lIDwtIHBhcmFtcyRzYW1wbGVfbmFtZQp9CgojIyMjIyBSZWFkIGluIHJlZmVyZW5jZSBkYXRhc2V0cyBhbmQgbWVyZ2UgdGhlbSB3aXRoIHNhbXBsZSBkYXRhLiBUaGlzIHBhcnQgb3V0cHV0cyBhIHZlY3RvciB3aXRoIGZpcnN0IGVsZW1lbnQgY29udGFpbmluZyB0aGUgbWVyZ2VkIGRhdGEgYW5kIHNlY29uZCBlbGVtZW50IGNvbnRhaW5pbmcgbWVyZ2VkIHRhcmdldHMgaW5mbwpyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV0gPC0gY29tYmluZURhdGFzZXRzKHNhbXBsZV9uYW1lPXNhbXBsZV9uYW1lLCBzYW1wbGVfY291bnRzPWNvdW50cywgcmVmX2RhdGE9cmVmX2RhdGFzZXQsIHJlcG9ydF9kaXIgPSByZXN1bHRzX2RpciwgZGF0YXNldCA9IGRhdGFzZXQpCm5hbWVzKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXSkgPC0gYygiY29tYmluZWRfZGF0YSIsICJzYW1wbGVfYW5ub3QiKQoKIyMjIyMgRGVmaW5lIGludGVybmFsLCBleHRlcm5hbCBhbmQgYWRkaXRpb24gY2FuY2VyIGdyb3VwIG5hbWVzIGJhc2VkIG9uIHRoZSB0YXJnZXRzIGRlZmluaXRpb24KaW50X2NhbmNlcl9ncm91cCA8LSByZWZfZGF0YXNldCRpbnRfcmVmWzNdCmV4dF9jYW5jZXJfZ3JvdXAgPC0gcmVmX2RhdGFzZXQkZXh0X3JlZlszXQoKaWYgKCBsZW5ndGgodW5pcXVlKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXSRUYXJnZXQpKSA+IDMgKSB7CiAgCiAgYWRkX2NhbmNlcl9ncm91cCA8LSB1bmlxdWUocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sic2FtcGxlX2Fubm90Il1dJFRhcmdldClbMl0KfSBlbHNlIHsKICBhZGRfY2FuY2VyX2dyb3VwIDwtIE5VTEwKfQoKIyMjIyMgRGVmaW5lIHRoZSBjYW5jZXIgZ3JvdXAgdG8gYmUgdXNlZCB0byBjb21wYXJlIHBlci1nZW5lIGV4cHJlc3Npb24gdmFsdWVzIGFuZCByZXBvcnQgaW4gdGhlIHN1bW1hcnkgdGFibGVzCmlmICggZGF0YXNldCA9PSAiUEFBRCIgfHwgZGF0YXNldCA9PSAiUEFBRC1JUE1OIiB8fCBkYXRhc2V0ID09ICJQQUFELU5FVCIgfHwgZGF0YXNldCA9PSAiUEFBRC1BQ0MiICkgewogIGNvbXBfY2FuY2VyX2dyb3VwIDwtIGludF9jYW5jZXJfZ3JvdXAKfSBlbHNlIHsKICBjb21wX2NhbmNlcl9ncm91cCA8LSBleHRfY2FuY2VyX2dyb3VwCn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShjb3VudHMsIHR4MmVuc2VtYmwpCmBgYAoKYGBge3IgbXlzcWxfcG9wdWxhdGUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIEluaXRpYXRlIE15U1FMIGNvbW1hbmQgdG8gcG9wdWxhdGUgUk5BLXNlcSBkYXRhIHBvcnRhbApteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAoIiMjIyBNeVNRTCBjb21tYW5kIHRvIGluc2VydCBkYXRhIGZvciBzYW1wbGUgXCIiLCBzYW1wbGVfbmFtZSwgIlwiXG51c2UgcGllZGI7XG5JTlNFUlQgSU5UTyBSTkFzZXFfcmVwb3J0cyAoIElEICxQbGF0Zm9ybSwgUGF0aWVudElELCBTYW1wbGVJRCwgQ2FuY2VyLCBTb3VyY2UsIFByb2plY3QsIFJlcG9ydCwgUE1JRCwgQW5hbHlzaXMsIFN1bW1hcnksIERhdGUgKSBWQUxVRVMgKCAxMDAwMDAwLCBcIlJOQV9zZXFcIiIpCm15c3FsX3BvcHVsYXRlX3VwZGF0ZSA8LSAiT04gRFVQTElDQVRFIEtFWSBVUERBVEUgSUQ9MTAwMDAwMCAsUGxhdGZvcm09XCJSTkFfc2VxXCIiCgojIyMjIyBVcGRhdGUgTXlTUUwgY29tbWVuZCB0byBwb3B1bGF0ZSBSTkEtc2VxIGRhdGEgcG9ydGFsCm15c3FsX3BvcHVsYXRlIDwtIHBhc3RlMChteXNxbF9wb3B1bGF0ZSwgIiwgXCIiLCBzdWJqZWN0SUQsICJcIiwgXCIiLCBzYW1wbGVfbmFtZSwgIlwiLCBcIiIsIHBhcmFtcyRkYXRhc2V0ICwgIlwiLCBcIiIsIHBhcmFtcyRzYW1wbGVfc291cmNlICwgIlwiLCBcIiIsIHBhcmFtcyRwcm9qZWN0ICwgIlwiLCBcIiIsIHBhc3RlMChzYW1wbGVfbmFtZSwgIi5STkFzZXFfcmVwb3J0Lmh0bWwiKSwgIlwiLCBcIiIsIHNhbXBsZV9uYW1lLCAiXCIsIFwiIiAgKQpteXNxbF9wb3B1bGF0ZV91cGRhdGUgPC0gIHBhc3RlMChteXNxbF9wb3B1bGF0ZV91cGRhdGUsICIsIFBhdGllbnRJRD1cIiIsIHN1YmplY3RJRCwgIlwiLCBTYW1wbGVJRD1cIiIsIHNhbXBsZV9uYW1lLCAiXCIsIENhbmNlcj1cIiIsIHBhcmFtcyRkYXRhc2V0ICwgIlwiLCBTb3VyY2U9XCIiLCBwYXJhbXMkc2FtcGxlX3NvdXJjZSAsICJcIiwgUHJvamVjdD1cIiIsIHBhcmFtcyRwcm9qZWN0ICwgIlwiLCBSZXBvcnQ9XCIiLCBwYXN0ZTAoc2FtcGxlX25hbWUsICIuUk5Bc2VxX3JlcG9ydC5odG1sIiksIlwiLCBQTUlEPVwiIiwgc2FtcGxlX25hbWUsICJcIiwgQW5hbHlzaXM9XCIiICApCmBgYAoKYGBge3IgdHJlYXRtZW50X2luZm8sIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA1LCBldmFsID0gcnVuQ2xpbmljYWxDaHVuayB9CiMjIyMjIFByYXBhcmUgZGF0YSBmb3IgdGhlIHRyZWF0bWVudCB0aW1lbGluZSBwbG90CiMjIyMjIFNlYXJjaCBmb3Igcm93IHdpdGggY2xpbmljYWwgaW5mbyBmb3IgaW52ZXN0aWdhdGVkIHBhdGllbnQKaWYgKCAhaXMubmEocGFyYW1zJGNsaW5pY2FsX2lkKSApIHsKICBzYW1wbGVJRC5jb2wgPC0gZ3JlcChwYXJhbXMkY2xpbmljYWxfaWQsIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNsaW5pY2FsX2luZm8iXV0pCn0gZWxzZSBpZiAoICFpcy5uYShwYXJhbXMkc3ViamVjdF9pZCkgKSB7CiAgc2FtcGxlSUQuY29sIDwtIGdyZXAocGFyYW1zJHN1YmplY3RfaWQsIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNsaW5pY2FsX2luZm8iXV0pCn0gZWxzZSBpZiAoICFpcy5uYShzdWJqZWN0SUQpICkgewogIHNhbXBsZUlELmNvbCA8LSBncmVwKHN1YmplY3RJRCwgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY2xpbmljYWxfaW5mbyJdXSkKfQoKcnVuQ2xpbmljYWxDaHVuayA8LSBGQUxTRQoKaWYgKCBsZW5ndGgoc2FtcGxlSUQuY29sKSA+IDAgKSB7CiAgCiAgIyMjIyMgSWRlbnRpZnkgY29sdW1uIGFuZCByb3cgd2l0aCBwYXRpZW50cyBkZXRhaWxzCiAgaWYgKCAhaXMubmEocGFyYW1zJGNsaW5pY2FsX2lkKSApIHsKICAgIHNhbXBsZUlELnJvdyA8LSBncmVwKHBhcmFtcyRjbGluaWNhbF9pZCwgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY2xpbmljYWxfaW5mbyJdXVssIHNhbXBsZUlELmNvbF0pCiAgfSBlbHNlIGlmICggIWlzLm5hKHBhcmFtcyRzdWJqZWN0X2lkKSApIHsKICAgIHNhbXBsZUlELnJvdyA8LSBncmVwKHBhcmFtcyRzdWJqZWN0X2lkLCByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjbGluaWNhbF9pbmZvIl1dWywgc2FtcGxlSUQuY29sXSkKICB9IGVsc2UgaWYgKCAhaXMubmEoc3ViamVjdElEKSApIHsKICAgIHNhbXBsZUlELnJvdyA8LSBncmVwKHN1YmplY3RJRCwgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY2xpbmljYWxfaW5mbyJdXVssIHNhbXBsZUlELmNvbF0pCiAgfQogIAogIGNsaW5pY2FsX2luZm8gPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY2xpbmljYWxfaW5mbyJdXVsgc2FtcGxlSUQucm93LCBdCiAgCiAgIyMjIyMgUHJlcGFyZSBkYXRhIGZyYW1lIHN0cnVjdHVyZSBmb3IgcGxvdHRpbmcKICAjIyMjIyBEZWZpbmUgdHJlYXRtZW50IHR5cGVzCiAgdHJlYW10ZW50LnR5cGVzIDwtIG1ha2UubmFtZXMoYygiTkVPQURKVVZBTlQgUkVHSU1FTiIsICJBREpVVkFOVCBSRUdJTUVOIiwgIkZJUlNUIExJTkUgUkVHSU1FTiIsICJTRUNPTkQgTElORSBSRUdJTUVOIiwgIlRISVJEIExJTkUgUkVHSU1FTiIpKQogIHRyZWFtdGVudC50eXBlc19zaW1wbGUgPC0gYygiTmVvYWRqdXZhbnQiLCAiQWRqdXZhbnQiLCAiMXN0IGxpbmUiLCAiMm5kIGxpbmUiLCAiM3JkIGxpbmUiKQogIHRyZWFtdGVudC5kZiA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sID0gNCwgbnJvdyA9IDApKQogIGNvbG5hbWVzKHRyZWFtdGVudC5kZikgPC0gYygiVHJlYXRtZW50IiwgIlR5cGUiLCAiU3RhcnQiLCAiRW5kIikKCiAgZm9yICggaSBpbiAxOmxlbmd0aCh0cmVhbXRlbnQudHlwZXMpICkgewogICAgCiAgICAjIyMjIyBJZGVudGlmeSB0cmVhdG1lbnQgY29sdW1uIG51bWJlcgogICAgdHJlYW10ZW50LnR5cGVzLmNvbCA8LSBncmVwKHBhc3RlMCgiXiIsdHJlYW10ZW50LnR5cGVzW2ldLCAiJCIpLCBuYW1lcyhjbGluaWNhbF9pbmZvKSkKICAgIAogICAgIyMjIyMgQ2hlY2sgaG93IG1hbnkgdHJlYXRtZW50cyBvZiBwYXJ0aWN1bGFyIHR5cGUgd2VyZSB1c2VkCiAgICB0cmVhbXRlbnQudHlwZXMuZGV0YWlscyA8LSB1bmxpc3Qoc3Ryc3BsaXQoY2xpbmljYWxfaW5mb1ssIHRyZWFtdGVudC50eXBlcy5jb2xdLCBzcGxpdD0nLCcsIGZpeGVkPVRSVUUpKQogICAgCiAgICAjIyMjIyBBZGQgc3RhcnQgYW5kIGVuZCBpbmZvIGZvciBlYWNoIHRyZWF0bWVudAogICAgaWYgKCBhbnkoIWlzLm5hKHRyZWFtdGVudC50eXBlcy5kZXRhaWxzICksIG5hLnJtID0gRkFMU0UpICkgewogICAgICBmb3IgKCB0cmVhdG1lbnQgaW4gdHJlYW10ZW50LnR5cGVzLmRldGFpbHMgKSB7CiAgICAgICAgCiAgICAgICAgdHJlYW10ZW50LnN0YXJ0IDwtIGNsaW5pY2FsX2luZm9bLCB0cmVhbXRlbnQudHlwZXMuY29sKzFdCiAgICAgICAgdHJlYW10ZW50LmVuZCA8LSBjbGluaWNhbF9pbmZvWywgdHJlYW10ZW50LnR5cGVzLmNvbCsyXQoKICAgICAgICAjIyMjIyBVc2UgY3VycmVudCBkYXRhIGlmIHRyZWF0bWVudCBpcyBzdGlsbCBvbmdvaW5nCiAgICAgICAgdG9kYXkgPC0gYXMuY2hhcmFjdGVyKFN5cy5EYXRlKCkpCiAgICAgICAgdHJlYW10ZW50LmVuZFsgaXMubmEodHJlYW10ZW50LmVuZCkgXSA8LSB0b2RheQogICAgICAgIHRyZWFtdGVudC50bXAgPC0gZGF0YS5mcmFtZSggdHJlYXRtZW50LCB0cmVhdG1lbnQsIHRyZWFtdGVudC50eXBlc19zaW1wbGVbaV0sIHRyZWFtdGVudC5zdGFydCwgdHJlYW10ZW50LmVuZCkKICAgICAgICB0cmVhbXRlbnQuZGYgPC0gcmJpbmQoIHRyZWFtdGVudC5kZiwgdHJlYW10ZW50LnRtcCkKICAgICAgfQogICAgfQogIH0KICAKICBpZiAoIG5yb3codHJlYW10ZW50LmRmKSA+IDAgKSB7CiAgICAjIyMjIyBGb3Igc2VjdXJpdHkgcmVhc29ucyAod3J0IHBsb3RzIHRoYXQgZ28gdG8gUElFZGIpLCBjaGFuZ2UgdGhlIGRhdGVzIGJ1dCBwcmVzZXJ2ZSB0aGUgZHVyYXRpb24gb2YgdGhlIHRyZWF0bWVudHMKICAgICMjIyMjIEdldCB0aGUgZWFybGllc3QgdHJlYXRtZW50IGRhdGUgYW5kIHNldCBpdCBhcyBkYXkgMC4gVGhlbiwgY3JlYXRlIGZha2Ugc3RhcnQgYW5kIGVuZCBkYXRlcyBiYXNlZCBvbiB0aGUgdHJlYXRtZW50IGxlbmd0aAogICAgZGF5MCA8LSBzb3J0KHRyZWFtdGVudC5kZiR0cmVhbXRlbnQuc3RhcnQsIGRlY3JlYXNpbmcgPSBGQUxTRSlbMV0KICAgIHRyZWFtdGVudHMubGVuZ3RoIDwtIHRyZWFtdGVudC5kZiR0cmVhbXRlbnQuZW5kIC0gdHJlYW10ZW50LmRmJHRyZWFtdGVudC5zdGFydAogICAgdHJlYW10ZW50cy5yZXNldCA8LSBhcy5EYXRlKCIyMDAwLTAxLTAxIikgLSBkYXkwCiAgICB0cmVhbXRlbnQuZGYkdHJlYW10ZW50LnN0YXJ0IDwtIHRyZWFtdGVudC5kZiR0cmVhbXRlbnQuc3RhcnQgKyB0cmVhbXRlbnRzLnJlc2V0CiAgICB0cmVhbXRlbnQuZGYkdHJlYW10ZW50LmVuZCA8LSB0cmVhbXRlbnQuZGYkdHJlYW10ZW50LnN0YXJ0ICsgdHJlYW10ZW50cy5sZW5ndGgKICAgIG5hbWVzKHRyZWFtdGVudC5kZikgPC0gYygiVHJlYXRtZW50IiwgIkRydWciLCAiVHlwZSIsICJTdGFydCIsICAiRW5kIikKICAgIAogICAgIyMjIyMgQ3JlYXRlIGRpcmVjdG9yeSBmb3IgdGltZWxpbmUgcGxvdAogICAgUGxvdHNEaXIgPC0gcGFzdGUocmVzdWx0c19kaXIsICJjbGluaWNhbF9pbmZvIiwgc2VwID0gIi8iKQogICAgaWYgKCAhZmlsZS5leGlzdHMoUGxvdHNEaXIpICkgewogICAgICBkaXIuY3JlYXRlKFBsb3RzRGlyLCByZWN1cnNpdmU9VFJVRSkKICAgIH0KICAgICAgICAKICAgICMjIyMjIFJlY29yZCB0aGUgdGltZWxpbmUgcGxvdC4gTk9URSwgdGhlIG1vZGlmaWVkIGRhdGVzIGFyZSB1c2VkIGhlcmUKICAgIHRyZWF0bWVudF90aW1lbGluZSA8LSBsYXJlczo6cGxvdF90aW1lbGluZShldmVudCA9IHRyZWFtdGVudC5kZiRUcmVhdG1lbnQsIHN0YXJ0ID0gdHJlYW10ZW50LmRmJFN0YXJ0LCBlbmQgPSB0cmVhbXRlbnQuZGYkRW5kLCBsYWJlbCA9IE5BLCBncm91cCA9IHRyZWFtdGVudC5kZiRUeXBlLCB0aXRsZSA9ICIiLCBzdWJ0aXRsZSA9ICIiLCBzYXZlID0gRkFMU0UpCiAgICAKICAgICMjIyMjIFNhdmUgdGhlIHBsb3QgaW50byBwbmcgZmlsZS4gTk9URSwgdGhlIG1vZGlmaWVkIGRhdGVzIGFyZSB1c2VkIGhlcmUuIEFzIGRlZmF1bHQsIHRoZSBwbG90IGlzIHNhdmVkIGFzICJjdl90aW1lbGluZSIKICAgIGxhcmVzOjpwbG90X3RpbWVsaW5lKGV2ZW50ID0gdHJlYW10ZW50LmRmJFRyZWF0bWVudCwgc3RhcnQgPSB0cmVhbXRlbnQuZGYkU3RhcnQsIGVuZCA9IHRyZWFtdGVudC5kZiRFbmQsIGxhYmVsID0gTkEsIGdyb3VwID0gdHJlYW10ZW50LmRmJFR5cGUsIHRpdGxlID0gIiIsIHN1YnRpdGxlID0gIiIsIHNhdmUgPSBUUlVFLCBzdWJkaXIgPSAiY2xpbmljYWxfaW5mbyIpCiAgICAKICAgICMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQogICAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCiAgICAKICAgIGN2X3RpbWVsaW5lLnBuZyA8LSByZWFkUE5HKCJjbGluaWNhbF9pbmZvL2N2X3RpbWVsaW5lLnBuZyIsIG5hdGl2ZSA9IEZBTFNFLCBpbmZvID0gRkFMU0UpCiAgICAKICAgICMjIyMjIENoYW5nZSB0aGUgc2l6ZSBvZiB0aGUgdGltZWxpbmUgcG5nIHBsb3QgYW5kIHNhdmUgaXQgYXMgInRyZWF0bWVudF90aW1lbGluZS5wbmciCiAgICBwbmc6OndyaXRlUE5HKGN2X3RpbWVsaW5lLnBuZywgcGFzdGUoUGxvdHNEaXIsICJ0cmVhdG1lbnRfdGltZWxpbmUucG5nIiwgc2VwPSIvIiksIGRwaT0zMDApCiAgICAjcG5nKHBhc3RlKFBsb3RzRGlyLCAidHJlYXRtZW50X3RpbWVsaW5lLnBuZyIsIHNlcD0iLyIpLCB3aWR0aCA9IDkwMCwgaGVpZ2h0ID0gNjAwLCBwb2ludHNpemUgPSAwLjAwMDEsIHJlcz0zMDApCiAgICAjcGxvdChjdl90aW1lbGluZS5wbmcpCiAgICAjaW52aXNpYmxlKGRldi5vZmYoKSkKICAgIAogICAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKICAgIAogICAgIyMjIyMgUmVtb3ZlIHRoZSBvcmlnaW5hbCBwbG90IGZvbGRlcgogICAgc3lzdGVtKCJybSAtcmYgY2xpbmljYWxfaW5mbyIsIGlnbm9yZS5zdGRvdXQgPSBUUlVFLCBpZ25vcmUuc3RkZXJyID0gVFJVRSkKICAgIAogICAgcnVuQ2xpbmljYWxDaHVuayA8LSBUUlVFCiAgfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGxpc3QgPSBscyhwYXR0ZXJuPSdedHJlYW10ZW50LionKSkKcm0oY2xpbmljYWxfaW5mbywgY3ZfdGltZWxpbmUucG5nKQp9CmBgYAoKYGBge3IgY2FuY2VyX2dlbmVzX3ByZXAsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyMjIyMgQ29tYmluZSBVTUNDUiBjYW5jZXIgZ2VuZSBsaXN0IChodHRwczovL2dpdGh1Yi5jb20vdmxhZHNhdmVsaWV2L05HU19VdGlscy9ibG9iL21hc3Rlci9uZ3NfdXRpbHMvcmVmZXJlbmNlX2RhdGEva2V5X2dlbmVzL3VtY2NyX2NhbmNlcl9nZW5lcy4yMDE5LTAzLTIwLnRzdikgd2l0aCBPbmNvS0IgY2FuY2VyIGdlbmVzCmdlbmVzX2NhbmNlciA8LSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX29uY29rYiJdXQpnZW5lc19jYW5jZXIkVU1DQ1IgPC0gcmVwKCJObyIsIG5yb3coZ2VuZXNfY2FuY2VyKSkKZ2VuZXNfY2FuY2VyJE9uY29nZW5lIDwtIHJlcCgiLSIsIG5yb3coZ2VuZXNfY2FuY2VyKSkKZ2VuZXNfY2FuY2VyJFRTRyA8LSByZXAoIi0iLCBucm93KGdlbmVzX2NhbmNlcikpCmdlbmVzX2NhbmNlciRGdXNpb24gPC0gcmVwKCItIiwgbnJvdyhnZW5lc19jYW5jZXIpKQpnZW5lc19jYW5jZXIkR2VybWxpbmUgPC0gcmVwKCItIiwgbnJvdyhnZW5lc19jYW5jZXIpKQoKIyMjIyMgRmxhZyBPbmNvZ2VuZXMsIFRTR3MgYW5kIGZ1c2lvbiBnZW5lcyBpbiB0aGUgVU1DQ1IgY2FuY2VyIGdlbmVzIGxpc3QgKGh0dHBzOi8vZ2l0aHViLmNvbS92bGFkc2F2ZWxpZXYvTkdTX1V0aWxzL2Jsb2IvbWFzdGVyL25nc191dGlscy9yZWZlcmVuY2VfZGF0YS9rZXlfZ2VuZXMvdW1jY3JfY2FuY2VyX2dlbmVzLjIwMTktMDMtMjAudHN2KQpyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSRnZXJtIDwtIGdzdWIoIlRSVUUiLCAiWWVzIiwgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kZ2VybSkKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kZ2VybSA8LSBnc3ViKCJGQUxTRSIsICItIiwgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kZ2VybSkKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kZnVzaW9uIDwtIGdzdWIoIlRSVUUiLCAiWWVzIiwgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kZnVzaW9uKQpyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSRmdXNpb24gPC0gZ3N1YigiRkFMU0UiLCAiLSIsIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dJGZ1c2lvbikKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kdHVtb3JzdXBwcmVzc29yIDwtIGdzdWIoIlRSVUUiLCAiWWVzIiwgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kdHVtb3JzdXBwcmVzc29yKQpyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSR0dW1vcnN1cHByZXNzb3IgPC0gZ3N1YigiRkFMU0UiLCAiLSIsIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dJHR1bW9yc3VwcHJlc3NvcikKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kb25jb2dlbmUgPC0gZ3N1YigiVFJVRSIsICJZZXMiLCByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSRvbmNvZ2VuZSkKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kb25jb2dlbmUgPC0gZ3N1YigiRkFMU0UiLCAiLSIsIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dJG9uY29nZW5lKQoKZm9yICggZ2VuZSBpbiB1bmxpc3QocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kc3ltYm9sICkgKSB7CiAgIyMjIyMgQ2hlY2sgaWYgdGhlIFVNQ0NSIGdlbmVzIGlzIGFscmVhZHkgcmVwb3J0ZWQgaW4gT25jb0tCCiAgaWYgKCBnZW5lICVpbiUgZ2VuZXNfY2FuY2VyJEh1Z28uU3ltYm9sICkgewogICAKICAgIGdlbmVzX2NhbmNlclsgZ2VuZXNfY2FuY2VyJEh1Z28uU3ltYm9sPT1nZW5lLCBdJFVNQ0NSIDwtICJZZXMiCiAgICBnZW5lc19jYW5jZXJbIGdlbmVzX2NhbmNlciRIdWdvLlN5bWJvbD09Z2VuZSwgXSRPbmNvZ2VuZSA8LSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSRvbmNvZ2VuZVtyZWZfZ2VuZXMubGlzdFtbICJnZW5lc19jYW5jZXIiXV0kc3ltYm9sPT1nZW5lXQogICAgZ2VuZXNfY2FuY2VyWyBnZW5lc19jYW5jZXIkSHVnby5TeW1ib2w9PWdlbmUsIF0kVFNHIDwtIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dJHR1bW9yc3VwcHJlc3NvcltyZWZfZ2VuZXMubGlzdFtbICJnZW5lc19jYW5jZXIiXV0kc3ltYm9sPT1nZW5lXQogICAgZ2VuZXNfY2FuY2VyWyBnZW5lc19jYW5jZXIkSHVnby5TeW1ib2w9PWdlbmUsIF0kRnVzaW9uIDwtIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dJGZ1c2lvbltyZWZfZ2VuZXMubGlzdFtbICJnZW5lc19jYW5jZXIiXV0kc3ltYm9sPT1nZW5lXQogICAgZ2VuZXNfY2FuY2VyWyBnZW5lc19jYW5jZXIkSHVnby5TeW1ib2w9PWdlbmUsIF0kR2VybWxpbmUgPC0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kZ2VybVtyZWZfZ2VuZXMubGlzdFtbICJnZW5lc19jYW5jZXIiXV0kc3ltYm9sPT1nZW5lXQogICAgCiAgICBnZW5lc19jYW5jZXJbIGdlbmVzX2NhbmNlciRIdWdvLlN5bWJvbD09Z2VuZSwgMl0gPC0gYXMubnVtZXJpYyhnZW5lc19jYW5jZXJbIGdlbmVzX2NhbmNlciRIdWdvLlN5bWJvbD09Z2VuZSwgMl0pICsgMQogICAgCiAgIyMjIyMgQWRkIGlmIG5vdCBwcmVzZW50CiAgfSBlbHNlIHsKICAgIGdlbmVzX2NhbmNlciA8LSByYmluZChnZW5lc19jYW5jZXIsIGMoZ2VuZSwgMSwgIk5vIiwgcmVwKCIiLCA4KSwgIlllcyIpKQogICAgZ2VuZXNfY2FuY2VyWyBnZW5lc19jYW5jZXIkSHVnby5TeW1ib2w9PWdlbmUsIF0kT25jb2dlbmUgPC0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0kb25jb2dlbmVbcmVmX2dlbmVzLmxpc3RbWyAiZ2VuZXNfY2FuY2VyIl1dJHN5bWJvbD09Z2VuZV0KICAgIGdlbmVzX2NhbmNlclsgZ2VuZXNfY2FuY2VyJEh1Z28uU3ltYm9sPT1nZW5lLCBdJFRTRyA8LSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSR0dW1vcnN1cHByZXNzb3JbcmVmX2dlbmVzLmxpc3RbWyAiZ2VuZXNfY2FuY2VyIl1dJHN5bWJvbD09Z2VuZV0KICAgIGdlbmVzX2NhbmNlclsgZ2VuZXNfY2FuY2VyJEh1Z28uU3ltYm9sPT1nZW5lLCBdJEZ1c2lvbiA8LSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSRmdXNpb25bcmVmX2dlbmVzLmxpc3RbWyAiZ2VuZXNfY2FuY2VyIl1dJHN5bWJvbD09Z2VuZV0KICAgIGdlbmVzX2NhbmNlclsgZ2VuZXNfY2FuY2VyJEh1Z28uU3ltYm9sPT1nZW5lLCBdJEdlcm1saW5lIDwtIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dJGdlcm1bcmVmX2dlbmVzLmxpc3RbWyAiZ2VuZXNfY2FuY2VyIl1dJHN5bWJvbD09Z2VuZV0KICB9Cn0KCiMjIyMjIE1ha2UgdGhlIGRhdGEgZnJhbWUgdG8gbG9vayBuaWNlcgpyb3duYW1lcyhnZW5lc19jYW5jZXIpIDwtIGdlbmVzX2NhbmNlciRIdWdvLlN5bWJvbApuYW1lcyhnZW5lc19jYW5jZXIpIDwtIGMoIkdlbmUiLCAiR2VuZSBwYW5lbHMgbm8uIiwgIk9uY29LQiIsICJPbmNvZ2VuZSAoT25jb0tCKSIsICJUU0cgKE9uY29LQikiLCAiTVNLLUlNUEFDVCIsICJNU0stSEVNRSIsICJGb3VuZGF0aW9uIE9uZSIsICJGb3VuZGF0aW9uIE9uZSBIZW1lIiwgIlZvZ2Vsc3RlaW4iLCAiU2FuZ2VyIENHQyIsICJVTUNDUiIsICJPbmNvZ2VuZSIsICJUU0ciLCAiRnVzaW9uIiwgIkdlcm1saW5lIikKZ2VuZXNfY2FuY2VyIDwtIGdlbmVzX2NhbmNlclssYygiT25jb2dlbmUiLCAiVFNHIiwgIkZ1c2lvbiIsICJHZXJtbGluZSIsICJHZW5lIHBhbmVscyBuby4iLCAiVU1DQ1IiLCAiT25jb0tCIiwgIk1TSy1JTVBBQ1QiLCAiTVNLLUhFTUUiLCAiRm91bmRhdGlvbiBPbmUiLCAiRm91bmRhdGlvbiBPbmUgSGVtZSIsICJWb2dlbHN0ZWluIiwgIlNhbmdlciBDR0MiKV0KZ2VuZXNfY2FuY2VyWyBnZW5lc19jYW5jZXI9PSJObyIgXSA8LSAiLSIKZ2VuZXNfY2FuY2VyWyBnZW5lc19jYW5jZXI9PSIiIF0gPC0gIi0iCgpyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSA8LSBnZW5lc19jYW5jZXIKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0gPC0gZ2VuZXNfY2FuY2VyWyByb3duYW1lcyhnZW5lc19jYW5jZXIpICVpbiUgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0kSHVnby5TeW1ib2wsIF0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShnZW5lc19jYW5jZXIpCmBgYAoKYGBge3IgZ29pX3N1bW1hcnksIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyMjIyMgUmVjb3JkIGFsbCBnZW5lcyBvZiBpbnRlcmVzdCB0byBtYWtlIHN1cmUgdGhhdCB0aGVzZSBhcmUgbm90IGZpbHRlcmVkIG91dCBkdXJpbmcgcmVhZCBjb3VudHMgZGF0YSBwcm9jZXNzaW5nCiMgUENHUiBhbm5vdGF0aW9uIG9mIG11dGF0ZWQgZ2VuZXMgaW4gZ2l2ZW4gcGF0aWVudCBiYXNlZCBvbiBQQ0dSIHJlcG9ydCwgaW5jbHVkaW5nIG9ubHkgdGhvc2Ugd2l0aCB2YXJpYW50cyBjbGFzc2lmaWVkIGFjY29yZGluZyB0byB1c2VyLWRlZmluZWQgdGllcgppZiAoIHJ1blBjZ3JDaHVuayApIHsKICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kTXV0YXRlZCA8LSB1bmlxdWUocmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dWyByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kVElFUiAlaW4lIGMoMTpwYXJhbXMkcGNncl90aWVyKSwgXSRTWU1CT0wpCiAgCiAgIyMjIyMgSW5jbHVkZSBzcGxpY2UgcmVnaW9uIHZhcmlhbnRzCiAgaWYgKCBwYXJhbXMkcGNncl9zcGxpY2VfdmFycyApIHsKICAgIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRNdXRhdGVkIDwtIHVuaXF1ZSggYyhyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kTXV0YXRlZCwgIHJlZl9nZW5lcy5saXN0W1sicGNnciJdXVsgZ3JlcGwoIk5PTkNPRElORy4qc3BsaWNlIHJlZ2lvbiIsIHBhc3RlMChyZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kVElFUiwgIi4iLCByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kQ09OU0VRVUVOQ0UpLCBmaXhlZCA9IEZBTFNFKSwgXSRTWU1CT0wpICkKICB9CiAgCiAgIyMjIyMgUmVtb3ZlIE5BcwogIGlmICggbGVuZ3RoKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRNdXRhdGVkKSA+IDAgKSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kTXV0YXRlZCA8LSByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kTXV0YXRlZFsgIShpcy5uYShyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kTXV0YXRlZCkpIF0KICB9IGVsc2UgewogICAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJE11dGF0ZWQgPC0gTlVMTAogIH0KfQogICAgCiMgQVJSSUJBIGFuZCBQSVpaTFkgYW5ub3RhdGlvbiBvZiBnZW5lIGZ1c2lvbiBldmVudHMgZGV0ZWN0ZWQgaW4gZ2l2ZW4gcGF0aWVudCBiYXNlZCBvbiBQSVpaTFkgcmVzdWx0cwppZiAoIHJ1bkZ1c2lvbkNodW5rICkgewogIAogIGlmICggcnVuQXJyaWJhQ2h1bmsgKSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kRnVzaW9uIDwtIHVuaXF1ZShjKGFzLmNoYXJhY3RlcihyZWZfZ2VuZXMubGlzdFtbImFycmliYSJdXSRYLmdlbmUxKSwgYXMuY2hhcmFjdGVyKHJlZl9nZW5lcy5saXN0W1siYXJyaWJhIl1dJGdlbmUyKSkpCiAgfSBlbHNlIHsKICAgIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRGdXNpb24gPC0gTlVMTAogIH0KICAKICBpZiAoIHJ1blBpenpseUNodW5rICkgewogICAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEZ1c2lvbiA8LSB1bmlxdWUoYyhyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kRnVzaW9uLCBhcy5jaGFyYWN0ZXIocmVmX2dlbmVzLmxpc3RbWyJwaXp6bHkiXV0kZ2VuZUEubmFtZSksIGFzLmNoYXJhY3RlcihyZWZfZ2VuZXMubGlzdFtbInBpenpseSJdXSRnZW5lQi5uYW1lKSkpCiAgfQogIAogIGlmICggcnVuRHJhZ2VuRnVzaW9uQ2h1bmsgKSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kRnVzaW9uIDwtIHVuaXF1ZShjKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRGdXNpb24sIGFzLmNoYXJhY3RlcihyZWZfZ2VuZXMubGlzdFtbImRyYWdlbkZ1c2lvbiJdXSRnZW5lMSksIGFzLmNoYXJhY3RlcihyZWZfZ2VuZXMubGlzdFtbImRyYWdlbkZ1c2lvbiJdXSRnZW5lMikpKQogIH0KICAKICAjIyMjIyBSZW1vdmUgTkFzCiAgaWYgKCBsZW5ndGgocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJE11dGF0ZWQpID4gMCApIHsKICAgIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRGdXNpb24gPC0gcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEZ1c2lvblsgIShpcy5uYShyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kRnVzaW9uKSkgXQogIH0gZWxzZSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kRnVzaW9uIDwtIE5VTEwKICB9Cn0KCiMgTUFOVEEgYW5ub3RhdGlvbiBvZiBzdHJ1Y3R1cmFsIHZhcmlhbnRzIChTVnMpIHdpdGggYWZmZWN0ZWQgZ2VuZXMgaW4gZ2l2ZW4gcGF0aWVudCBiYXNlZCBvbiBNQU5UQSByZXN1bHRzCmlmICggcnVuU1ZzQ2h1bmsgKSB7CiAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJFNWIDwtIHJlZl9nZW5lcy5saXN0W1sibWFudGEiXV0KICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kU1YgPC0gcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJFNWWyByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kU1YkR2VuZSAhPSAiIiwgIF0kR2VuZQogICMgLi4uYW5kIGRpc3Rpbmd1aXNoIGNsYXNzaWZpZWQgYnkgTUFOVEEgYXMgZnVzaW9uIGdlbmVzCiAgCiAgIyMjIyMgUmVtb3ZlIE5BcwogIGlmICggbGVuZ3RoKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRTVikgPiAwICkgewogICAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJFNWIDwtIHVuaXF1ZSh1bmxpc3Qoc3Ryc3BsaXQocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJFNWLCBzcGxpdD0nJicsIGZpeGVkPVRSVUUpKSkKICAgIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRTViA8LSByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kU1ZbICEoaXMubmEocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJFNWKSkgXQogIH0gZWxzZSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kU1YgPC0gTlVMTAogIH0KfQoKIyBQVVJQTEUgYW5ub3RhdGlvbiBvZiBjb3B5LW51bWJlciAoQ04pIGFsdGVyZWQgZ2VuZXMgaW4gZ2l2ZW4gcGF0aWVudCBiYXNlZCBvbiBQVVJQTEUgcmVzdWx0cywgaW5jbHVkaW5nIG9ubHkgdGhvc2Ugd2l0aCBDTiB2YWx1ZXMgbWVldGluZyB1c2VyLWRlZmluZWQgdGhyZXNob2xkcwppZiAoIHJ1blB1cnBsZUNodW5rICkgewogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiA8LSByZWZfZ2VuZXMubGlzdFtbInB1cnBsZSJdXQogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiA8LSByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ05bIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiAlIWluJSAiIiwgIF0KICAKICAjIyMjIyBHZXQgdGhlIENOIG1lYW4KICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04kTWVhbkNvcHlOdW1iZXIgPC0gcm93TWVhbnMoY2JpbmQocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENOJE1pbkNvcHlOdW1iZXIsIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiRNYXhDb3B5TnVtYmVyKSkKICAgIAogICMjIyMjIERlYWwgd2l0aCBuZWdhdGl2ZSBDTiB2YWx1ZXMKICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04kTWVhbkNvcHlOdW1iZXJbIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiRNZWFuQ29weU51bWJlciA8IDAgXSA8LSAwCgogICMjIyMjIExpbWl0IHRoZSBkYXRhIHRvIGluY2x1ZGUgb25seSBjYW5jZXIgZ2VuZXMKICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04gPC0gcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENOWyByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04kR2VuZSAlaW4lIHJvd25hbWVzKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dKSwgXQoKICAjIyMjIyBLZWVwIG9ubHkgYWx0ZXJlZCBnZW5lcyB3aXRoIENOIHZhbHVlcyBiZWxvdyBsb3NzIHRocmVzaG9sZCAoZGVmYXVsdCA1dGggcGVyY2VudGlsZSkgYW5kIGFib3ZlIGdhaW4gdGhyZXNob2xkIChkZWZhdWx0IDk1dGggcGVyY2VudGlsZSkKICBpZiAoIHBhcmFtcyRjbl9sb3NzID09IDUgJiYgcGFyYW1zJGNuX2dhaW4gPT0gOTUgKSB7CiAgICBjbl9kYXRhLmFsbC5wZXJjZW50IDwtIHF1YW50aWxlKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiRNZWFuQ29weU51bWJlciwgcHJvYnMgPSBzZXEoMCwgMSwgLjA1KSwgbmEucm0gPSBUUlVFKQogICAgY25fYm90dG9tIDwtIHJvdW5kKGNuX2RhdGEuYWxsLnBlcmNlbnRbMl0sIGRpZ2l0cyA9IDIpCiAgICBjbl90b3AgPC0gcm91bmQoY25fZGF0YS5hbGwucGVyY2VudFsyMF0sIGRpZ2l0cyA9IDIpCiAgCiAgfSBlbHNlIHsKICAgIGNuX2JvdHRvbSA8LSBwYXJhbXMkY25fbG9zcwogICAgY25fdG9wIDwtIHBhcmFtcyRjbl9nYWluCiAgfQogIAogICMjIyMjIElmIHRoZSBkaWZmZXJlbmNlIGlzIDAgdGhlbiBpbmNyZWFzZS9kZWNyZWFzZSB0aHJlc2hvbGQgYnkgMQogIGlmICAoIGFicyhjbl90b3AtY25fYm90dG9tKSA9PSAwICkgewogICAgY25fdG9wIDwtIGNuX3RvcCArIDEKICAgIGNuX2JvdHRvbSA8LSBjbl9ib3R0b20gLSAxCiAgfQogIAogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiA8LSB1bmlxdWUocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENOWyByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04kTWVhbkNvcHlOdW1iZXIgPD0gY25fYm90dG9tIHwgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENOJE1lYW5Db3B5TnVtYmVyID49IGNuX3RvcCwgXSRHZW5lKQogIAogICMjIyMjIFJlbW92ZSBOQXMKICBpZiAoIGxlbmd0aChyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04pID4gMCApIHsKICAgIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiA8LSByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ05bICEoaXMubmEocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENOKSkgXQogIH0gZWxzZSB7CiAgICByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04gPC0gTlVMTAogIH0KfQoKIyBJbW11bmUgcmVwb25zZSBtYXJrZXJzCnJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRJbW11bmUgPC0gdW5pcXVlKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzJFNZTUJPTCkKCmlmICggcGFyYW1zJGltbXVub2dyYW0gKSB7CiAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEltbXVuZSA8LSB1bmlxdWUoYyhyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kSW1tdW5lLCByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bm9ncmFtJFNZTUJPTCkpCiAgCiAgIyMjIyMgUmVtb3ZlIE5BcwogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRJbW11bmUgPC0gcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEltbXVuZVsgIShpcy5uYShyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kSW1tdW5lKSkgXQp9CgojIEhSRCAoaG9tb2xvZ291cyByZWNvbWJpbmF0aW9uIGRlZmljaWVuY3kpIGdlbmVzCnJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRIUkQgPC0gdW5pcXVlKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaHJkIl1dJFNZTUJPTCkKCiMjIyMjIFJlbW92ZSBOQXMKcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEhSRCA8LSByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kSFJEWyAhKGlzLm5hKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRIUkQpKSBdCiAgCiMgQ2FuY2VyIGdlbmVzIGRlcml2ZWQgZnJvbSBVTUNDUiBDYW5jZXIgR2VuZSBsaXN0IChodHRwczovL2dpdGh1Yi5jb20vdmxhZHNhdmVsaWV2L05HU19VdGlscy9ibG9iL21hc3Rlci9uZ3NfdXRpbHMvcmVmZXJlbmNlX2RhdGEva2V5X2dlbmVzL3VtY2NyX2NhbmNlcl9nZW5lcy4yMDE5LTAzLTIwLnRzdikgYW5kIGZyb20gT25jb0tCIHBvcnRhbCAoaHR0cDovL29uY29rYi5vcmcvIy9jYW5jZXJHZW5lcykKcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENhbmNlciA8LSByb3duYW1lcyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSkKCiMjIyMjIFJlbW92ZSBOQXMKcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENhbmNlciA8LSByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ2FuY2VyWyAhKGlzLm5hKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDYW5jZXIpKSBdCgojIyMjIyBSZWNvcmQgYWxsIGdlbmVzIG9mIGludGVyZXN0CmdlbmVzMmtlZXAgPC0gdW5pcXVlKCB1bmxpc3QocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dKSApCmBgYAoKYGBge3IgZ29pX2Fubm90YXRpb24sIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyMjIyMgR2V0IGdlbmUgc3ltYm9scyBmb3IgdGhlIGdlbmVzIG9mIGludGVyZXN0LiBUaGVzZSBnZW5lcyB3aWxsIG5vdCBiZSBmaWx0ZXJlZCBvdXQgZHVlIHRvIGxvdy9pbnN1ZmZpY2llbnQgZXhwcmVzc2lvbgojIyMjIyBHZXQgZ2VuZXMgYW5ub3RhdGlvbiBhbmQgZ2Vub21pYyBsb2NhdGlvbnMKZWRiIDwtIGV2YWwocGFyc2UodGV4dCA9IHBhc3RlMCgiRW5zRGIuSHNhcGllbnMudiIsIHBhcmFtcyRlbnNlbWJsX3ZlcnNpb24pKSkKICAKIyMjIyMgR2V0IGtleXR5cGVzIGZvciBnZW5lIFNZTUJPTAprZXlzIDwtIGtleXMoZWRiLCBrZXl0eXBlPSJHRU5FSUQiKQogIAojIyMjIyBHZXQgZ2VuZXMgZ2Vub21pYyBjb29yZGlhbnRlcwpnZW5lX2luZm8gPC0gZW5zZW1ibGRiOjpzZWxlY3QoZWRiLCBrZXlzPWtleXMsIGNvbHVtbnM9YygiR0VORUlEIiwgIkdFTkVOQU1FIiksIGtleXR5cGU9IkdFTkVJRCIpCm5hbWVzKGdlbmVfaW5mbykgPC0gZ3N1YigiR0VORUlEIiwgIkVOU0VNQkwiLCBuYW1lcyhnZW5lX2luZm8pKQpuYW1lcyhnZW5lX2luZm8pIDwtIGdzdWIoIkdFTkVOQU1FIiwgIlNZTUJPTCIsIG5hbWVzKGdlbmVfaW5mbykpCiAgCiMjIyMjIExpbWl0IGdlbmVzIGFubm90YXRpb24gdG8gdGhlIGdlbmUgb2YgaW50ZXJlc3QKZ2VuZXMya2VlcCA8LSBnZW5lX2luZm9bIGdlbmVfaW5mbyRTWU1CT0wgJWluJSBnZW5lczJrZWVwLCAgXQogIAojIyMjIyBSZW1vdmUgcm93cyB3aXRoIGR1cGxpY2F0ZWQgRU5TRU1CTCBJRHMKZ2VuZXMya2VlcCA9IGdlbmVzMmtlZXBbIWR1cGxpY2F0ZWQoZ2VuZXMya2VlcCRFTlNFTUJMKSxdCnJvd25hbWVzKGdlbmVzMmtlZXApIDwtIGdlbmVzMmtlZXAkRU5TRU1CTAoKIyMjIyMgUmVtb3ZlIHJvd3Mgd2l0aCBkdXBsaWNhdGVkIGdlbmUgc3ltYm9scyAoWV9STkFzLCBTTk9ScywgTElOQzBzIGV0YykuIFByZWZlcmFibHkgc2VsZWN0IEVOU0VNQkwgSUQgdGhhdCBpcyB1c2VkIGluIHRoZSBjb3VudCBkYXRhCmdlbmVzMmtlZXAuY29tYmluZWRfZGF0YSA8LSBnZW5lczJrZWVwWyBnZW5lczJrZWVwJEVOU0VNQkwgJWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV0kY29tYmluZWRfZGF0YSksIF0KZ2VuZXMya2VlcCA8LSBnZW5lczJrZWVwWyBnZW5lczJrZWVwJFNZTUJPTCAlIWluJSBnZW5lczJrZWVwLmNvbWJpbmVkX2RhdGEkU1lNQk9MLCBdCmdlbmVzMmtlZXAgPC0gIGdlbmVzMmtlZXBbIWR1cGxpY2F0ZWQoZ2VuZXMya2VlcCRTWU1CT0wpLF0KZ2VuZXMya2VlcCA8LSByYmluZChnZW5lczJrZWVwLmNvbWJpbmVkX2RhdGEsIGdlbmVzMmtlZXApCgojIyMjIyBBZGQgY29sdW1uIHRvIHN0b3JlIGluZm8gYWJvdXQgZmlsdGVyZWQgZ2VuZXMKZ2VuZXMya2VlcCRFWFAgPC0gVFJVRQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGVkYiwga2V5cywgZ2VuZV9pbmZvKQpgYGAKCmBgYHtyIGxpYnJhcnlfc2l6ZV9wbG90LCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZWNobyA9IFRSVUUsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gOX0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCiMjIyMjIEdlbmVyYXRlIGJhci1wbG90IGZvciBsaWJyYXJ5IHNpemUuIFRoZSBjb2xvdXJzIGluZGljYXRlIHNhbXBsZSBncm91cHMsIGFzIHByb3ZpZGVkIGluICpUYXJnZXQqIGNvbHVtbiBpbiB0aGUgc2FtcGxlIGFubm90YXRpb24gZmlsZQoKZGF0YSA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dCnRhcmdldCA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJzYW1wbGVfYW5ub3QiXV0KdGFyZ2V0JFRhcmdldFsgdGFyZ2V0JFRhcmdldD09c2FtcGxlX25hbWUgXSA8LSAiUGF0aWVudCIKcm93bmFtZXModGFyZ2V0KVsgcm93bmFtZXModGFyZ2V0KT09c2FtcGxlX25hbWUgXSA8LSAiUGF0aWVudCIKCiMjIyMjIENoYW5nZSB0aGUgZGF0YXNldHMgbGV2ZWxzIG9yZGVyCnRhcmdldCRUYXJnZXQgPC0gZmFjdG9yKHRhcmdldCRUYXJnZXQsIGxldmVscyA9IHVuaXF1ZSh0YXJnZXQkVGFyZ2V0KSkKCiMjIyMjIEFzc2lnbmUgY29sb3VycyB0byB0YXJnZXRzIGFuZCBkYXRhc2V0cwp0YXJnZXRzLmNvbG91ciA8LSBnZXRDb2xvdXJzKHRhcmdldCRUYXJnZXQpCgojIyMjIyBQcmVwYXJlIGRhdGEgZnJhbWUKZGF0YS5kZiA8LSBkYXRhLmZyYW1lKHJvd25hbWVzKHRhcmdldCksIGFzLm51bWVyaWMoY29sU3VtcyhkYXRhKSoxZS02KSwgdGFyZ2V0JFRhcmdldCkKY29sbmFtZXMoZGF0YS5kZikgPC0gYygiU2FtcGxlIiwgIkxpYnJhcnlfc2l6ZSIsICJUYXJnZXQiKQoKIyMjIyMgVGhlIGRlZmF1bHQgb3JkZXIgd2lsbCBiZSBhbHBoYWJldGl6ZWQgdW5sZXNzIHNwZWNpZmllZCBhcyBiZWxvdwpkYXRhLmRmJFNhbXBsZSA8LSBmYWN0b3IoZGF0YS5kZiRTYW1wbGUsIGxldmVscyA9IGRhdGEuZGZbWyJTYW1wbGUiXV0pCgpsaWJyYXJ5X3NpemUgPC0gcGxvdF9seShkYXRhLmRmLCB4ID0gflNhbXBsZSwgeSA9IH5MaWJyYXJ5X3NpemUsIGNvbG9yID0gflRhcmdldCwgY29sb3JzID0gdGFyZ2V0cy5jb2xvdXJbWzFdXSwgdHlwZSA9ICdiYXInLCB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gNDAwKSAlPiUKICBsYXlvdXQodGl0bGUgPSAiIiwgeGF4aXMgPSBsaXN0KCB0aWNrZm9udCA9IGxpc3Qoc2l6ZSA9IDEwKSwgdGl0bGUgPSAiIiwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSksIHlheGlzID0gbGlzdCh0aXRsZSA9ICJMaWJyYXJ5IHNpemUgKG1pbGxpb25zKSIpLCBtYXJnaW4gPSBsaXN0KGw9NTAsIHI9NTAsIGI9NTAsIHQ9NTAsIHBhZD00KSwgYXV0b3NpemUgPSBGLCBzaG93bGVnZW5kPVRSVUUsIGxlZ2VuZCA9IGxpc3Qob3JpZW50YXRpb24gPSAnaCcsIHkgPSBtYXgoZGF0YS5kZiRMaWJyYXJ5X3NpemUpLCBiZ2NvbG9yID0gIndoaXRlIikpCgojIyMjIyBDcmVhdGUgZGlyZWN0b3J5IGZvciBpbnB1dCBkYXRhIHBsb3RzClBsb3RzRGlyIDwtIHBhc3RlKHJlc3VsdHNfZGlyLCAiSW5wdXREYXRhUGxvdHMiLCBzZXAgPSAiLyIpCmlmICggIWZpbGUuZXhpc3RzKFBsb3RzRGlyKSApIHsKICBkaXIuY3JlYXRlKFBsb3RzRGlyLCByZWN1cnNpdmU9VFJVRSkKfQoKIyMjIyMgU2F2ZSBpbnRlcmFjdGl2ZSBwbG90IGFzIGh0bWwgZmlsZQpzYXZlV2lkZ2V0Rml4KGxpYnJhcnlfc2l6ZSwgZmlsZSA9IHBhc3RlKFBsb3RzRGlyLCAibGlicmFyeV9zaXplLmh0bWwiLCBzZXAgPSAiLyIpKQogIAojIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQpgYGAKCmBgYHtyIGRhdGFfdHJhbnNmb3JtYXRpb25fZmlsdGVyaW5nLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIEZpbHRlcmluZyB0byByZW1vdmUgbG93IGV4cHJlc3NlZCBnZW5lcy4gRm9yIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuZCByZWxhdGVkIGFuYWx5c2VzLCBnZW5lIGV4cHJlc3Npb24gaXMgcmFyZWx5IGNvbnNpZGVyZWQgYXQgdGhlIGxldmVsIG9mIHJhdyBjb3VudHMgc2luY2UgbGlicmFyaWVzIHNlcXVlbmNlZCBhdCBhIGdyZWF0ZXIgZGVwdGggd2lsbCByZXN1bHQgaW4gaGlnaGVyIGNvdW50cy4gUmF0aGVyLCBpdCBpcyBjb21tb24gcHJhY3RpY2UgdG8gdHJhbnNmb3JtIHJhdyBjb3VudHMgb250byBhIHNjYWxlIHRoYXQgYWNjb3VudHMgZm9yIHN1Y2ggbGlicmFyeSBzaXplIGRpZmZlcmVuY2VzLiBHZW5lcyB3aXRoIHZlcnkgbG93IGNvdW50cyBhY3Jvc3MgYWxsIGxpYnJhcmllcyBwcm92aWRlIGxpdHRsZSBldmlkZW5jZSBmb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24uIEluIHRoZSBiaW9sb2dpY2FsIHBvaW50IG9mIHZpZXcsIGEgZ2VuZSBtdXN0IGJlIGV4cHJlc3NlZCBhdCBzb21lIG1pbmltYWwgbGV2ZWwgYmVmb3JlIGl0IGlzIGxpa2VseSB0byBiZSB0cmFuc2xhdGVkIGludG8gYSBwcm90ZWluIG9yIHRvIGJlIGJpb2xvZ2ljYWxseSBpbXBvcnRhbnQuIEluIGFkZGl0aW9uLCB0aGUgcHJvbm91bmNlZCBkaXNjcmV0ZW5lcyBvZiB0aGVzZSBjb3VudHMgaW50ZXJmZXJlcyB3aXRoIHNvbWUgb2YgdGhlIHN0YXRpc3RpY2FsIGFwcHJveGltYXRpb25zIHRoYXQgYXJlIHVzZWQgbGF0ZXIgaW4gdGhlIHBpcGVsaW5lLiBUaGVzZSBnZW5lcyBzaG91bGQgYmUgZmlsdGVyZWQgb3V0IHByaW9yIHRvIGZ1cnRoZXIgYW5hbHlzaXMuIFVzZXJzIHNob3VsZCBmaWx0ZXIgd2l0aCBDUE0gcmF0aGVyIHRoYW4gZmlsdGVyaW5nIG9uIHRoZSBjb3VudHMgZGlyZWN0bHksIGFzIHRoZSBsYXR0ZXIgZG9lcyBub3QgYWNjb3VudCBmb3IgZGlmZmVyZW5jZXMgaW4gbGlicmFyeSBzaXplcyBiZXR3ZWVuIHNhbXBsZXMuIEZvciBpbnN0YW5jZSBmb3IgdGhlIENQTS10cmFuc2Zvcm1lZCBkYXRhIHdlIGtlZXAgb25seSBnZW5lcyB0aGF0IGhhdmUgQ1BNIG9mIDEKCiMjIyMjIFRyYW5zZm9ybWF0aW9uIHRvIENQTSBvciBUUE0gc2NhbGUgKHNlZSB0aGVzZSBibG9ncyBmb3IgZGV0YWlscyBodHRwczovL3d3dy5ybmEtc2VxYmxvZy5jb20vcnBrbS1mcGttLWFuZC10cG0tY2xlYXJseS1leHBsYWluZWQvIGFuZCBodHRwczovL2hhcm9sZHBpbWVudGVsLndvcmRwcmVzcy5jb20vMjAxNC8wNS8wOC93aGF0LXRoZS1mcGttLWEtcmV2aWV3LXJuYS1zZXEtZXhwcmVzc2lvbi11bml0cy8gKS4gIENQTSA9IENvdW50cyBQZXIgTWlsbGlvbiwgIFRQTSA9IFRyYW5zY3JpcHRzIFBlciBLaWxvYmFzZSBNaWxsaW9uLiAKCiMjIyMjIEZvciBjb3VudHMgZGF0YSBwcm9jZXNzaW5nIGNvbnNpZGVyIHRoZSBpbnZlc3RpZ2F0ZWQgc2FtcGxlIGFuZCBpbnRlcm5hbCByZWZlcmVuY2UgY29ob3J0IGFzIG9uZSBncm91cCAgKHJlZ2FyZGxlc3Mgb2YgdGhlIGludmVzdGlnYXRlZCBwYXRpZW50IHRpc3NpZSBvcmlnaW4pLCBhbmQgVENHQSBkYXRhIChvZiBhbnkgY2FuY2VyIHR5cGUpIGFzIGFub3RoZXIgZ3JvdXAuIFRoaXMgaXMgdG8gZmFjaWxpdGF0ZSBiYXRjaC1lZmZlY3RzIChyZWxhdGVkIHdpdGggdGVjaG5pY2FsIGFzcGVjdHMpIGNvcnJlY3Rpb24gcHJvY2Vzcwp0YXJnZXRfbW9kIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQp0YXJnZXRfbW9kJERhdGFzZXQgPC0gZ3N1YihzYW1wbGVfbmFtZSwgaW50X2NhbmNlcl9ncm91cCwgdGFyZ2V0X21vZCREYXRhc2V0KQp0YXJnZXRzX21vZC5saXN0IDwtIHVuaXF1ZSh0YXJnZXRfbW9kJERhdGFzZXQpCgojIyMjIyBDcmVhdGUgbGlzdHMgd2l0aCBwcm9jZXNzZWQgZGF0YSBlYWNoIGdyb3VwCnkgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoKHRhcmdldHNfbW9kLmxpc3QpKQpuYW1lcyh5KSA8LSB0YXJnZXRzX21vZC5saXN0CgojIyMjIyBLZWVwIGluZm8gYWJvdXQgc2FtcGxlcyB3aXRoIHRoZSBsb3dlc3QgYW5kIGdyZWF0ZXMgY291bnRzIGZvciBkZWZpbmVkIENQTSB0aHJlc2hvbGQKY3BtLm1pbiA8LSByb3VuZChtaW4oYXMubnVtZXJpYyhjb2xTdW1zKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGEiXV0pKjFlLTYpKSwgZGlnaXRzPTApCmNwbS5tYXggPC0gcm91bmQobWF4KGFzLm51bWVyaWMoY29sU3VtcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dKSoxZS02KSksIGRpZ2l0cz0wKQoKIyMjIyBGb3IgZWFjaCBncm91cC4uLgpmb3IgKCBncm91cCBpbiB0YXJnZXRzX21vZC5saXN0ICkgewogICAgdGFyZ2V0IDwtIHRhcmdldF9tb2RbIHRhcmdldF9tb2QkRGF0YXNldD09Z3JvdXAsIF0KICAgIGRhdGEgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXQogICAgZGF0YSA8LSBkYXRhWyAsIHRhcmdldF9tb2QkRGF0YXNldD09Z3JvdXBdCiAgICAKICAjIyMjIyBDUE0gdHJhbnNmb3JtYXRpb24gYW5kIGZpbHRlcmluZwogIGlmICggcGFyYW1zJGZpbHRlciAmJiBwYXJhbXMkdHJhbnNmb3JtID09ICJDUE0iICkgewogICAgCiAgICAjIyMjIyBDcmVhdGUgRWRnZVIgREdFTGlzdCBvYmplY3QKICAgIHlbW2dyb3VwXV0gPC0gZWRnZVI6OkRHRUxpc3QoY291bnRzPWRhdGEsICBncm91cD10YXJnZXQkRGF0YXNldCkKICAgIAogICAgIyMjIyMgS2VlcCBnZW5lcyB3aXRoIENQTSBvZiBhdCBsZWFzdCAxIGluIG1vcmUgdGhhbiAxMCUgb2Ygc2FtcGxlcwogICAgZmlsdGVyLnRocmVzaG9sZCA8LSAxCiAgICBrZWVwIDwtIHJvd1N1bXMoZWRnZVI6OmNwbSh5W1tncm91cF1dKT5maWx0ZXIudGhyZXNob2xkKSA+PSBuY29sKGRhdGEpLzEwCiAgICAKICAgICMjIyMjIE5vdGUgd2hpY2ggZ2VuZXMgb2YgaW50ZXJlc3QgYXJlIG5vdCBleHByZXNzZWQKICAgIGdlbmVzMmtlZXAkRVhQWyByb3duYW1lcyhnZW5lczJrZWVwKSAlIWluJSBuYW1lcyhrZWVwKSBdIDwtIEZBTFNFCiAgICAKICAgICMjIyMjIEtlZXAgdGhlIGdlbmVzIG9mIGludGVyZXN0IHRvbwogICAga2VlcFsgbmFtZXMoa2VlcCkgJWluJSByb3duYW1lcyhnZW5lczJrZWVwKSBdIDwtIFRSVUUKICAgIHlbW2dyb3VwXV0kZmlsdGVyZWQgPC0geVtbZ3JvdXBdXVtrZWVwLCAsIGtlZXAubGliLnNpemVzPUZBTFNFXQogICAgCiAgICAjIyMjIyBUcmFuc2Zvcm0gdGhlIHJhdy1zY2FsZSB0byBDUE0uIEFkZCBzbWFsbCBvZmZzZXQgdG8gZWFjaCBvYnNlcnZhdGlvbiB0byBhdm9pZCB0YWtpbmcgbG9nIG9mIHplcm8KICAgIHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQgPC0gZWRnZVI6OmNwbSh5W1tncm91cF1dLCBub3JtYWxpemVkLmxpYi5zaXplcz1GQUxTRSwgbG9nPXBhcmFtcyRsb2csIHByaW9yLmNvdW50PTAuMjUpCiAgICB5W1tncm91cF1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkIDwtIGVkZ2VSOjpjcG0oeVtbZ3JvdXBdXSRmaWx0ZXJlZCwgbm9ybWFsaXplZC5saWIuc2l6ZXM9RkFMU0UsIGxvZz1wYXJhbXMkbG9nLCBwcmlvci5jb3VudD0wLjI1KQogIAogICMjIyMjIENQTSB0cmFuc2Zvcm1hdGlvbiB3aXRob3V0IGZpbHRlcmluZwogIH0gZWxzZSBpZiAoICFwYXJhbXMkZmlsdGVyICYmIHBhcmFtcyR0cmFuc2Zvcm0gPT0gIkNQTSIgKSB7CiAgICAjIyMjIyBDcmVhdGUgRWRnZVIgREdFTGlzdCBvYmplY3QKICAgIHlbW2dyb3VwXV0gPC0gZWRnZVI6OkRHRUxpc3QoY291bnRzPWRhdGEsICBncm91cD10YXJnZXQkRGF0YXNldCkKICAgIAogICAgIyMjIyMgVHJhbnNmb3JtIHRoZSByYXctc2NhbGUgdG8gQ1BNLiBBZGQgc21hbGwgb2Zmc2V0IHRvIGVhY2ggb2JzZXJ2YXRpb24gdG8gYXZvaWQgdGFraW5nIGxvZyBvZiB6ZXJvCiAgICB5W1tncm91cF1dJHRyYW5zZm9ybWVkIDwtIGVkZ2VSOjpjcG0oeVtbZ3JvdXBdXSwgbm9ybWFsaXplZC5saWIuc2l6ZXM9RkFMU0UsIGxvZz1wYXJhbXMkbG9nLCBwcmlvci5jb3VudD0wLjI1KQogICAgCiAgIyMjIyMgVFBNIGRhdGEgdHJhbnNmb3JtYXRpb24uIFdlIGNhbiBjb252ZXJ0IFJQS00gdG8gVFBNIGluIHR3byBkaWZmZXJlbnQgd2F5czogZnJvbSBwcmUtY2FsY3VsYXRlZCBSUEtNLCBieSBkaXZpbmcgYnkgdGhlIHN1bSBvZiBSUEtNIHZhbHVlcywgb3IgZGlyZWN0bHkgZnJvbSB0aGUgbm9ybWFsaXplZCBjb3VudHMuIEhlcmUgd2UgY2FsY3VsYXRlIFRQTSBzdGFydGluZyBmcm9tIFJQS00gdmFsdWVzIGNvbXB1dGVkIHVzaW5nIGVkZ2VSJ3MgcnBrbSBmdW5jdGlvbiAoIGZyb20gaHR0cDovL2x1aXN2YWxlc2lsdmEuY29tL2RhdGFzaW1wbGUvcm5hLXNlcV91bml0cy5odG1sICkKICAjIyMjIyBUUE0gdHJhbnNmb3JtYXRpb24gd2l0aCBmaWx0ZXJpbmcKICB9IGVsc2UgaWYgKCBwYXJhbXMkZmlsdGVyICYmIHBhcmFtcyR0cmFuc2Zvcm0gPT0gIlRQTSIgKSB7CiAgICAKICAgICMjIyMjIEdldCBnZW5lcyBsZW5ndGhzCiAgICBlZGIgPC0gZXZhbChwYXJzZSh0ZXh0ID0gcGFzdGUwKCJFbnNEYi5Ic2FwaWVucy52IiwgcGFyYW1zJGVuc2VtYmxfdmVyc2lvbikpKQogICAgZ2VuZS5sZW5ndGggPC0gbGVuZ3RoT2YoZWRiLCBmaWx0ZXIgPSBHZW5lSWRGaWx0ZXIocm93bmFtZXMoZGF0YSkpKQogICAgCiAgICAjIyMjIyBDaGVjayBmb3Igd2hpY2ggZ2VuZXMgdGhlIGxlbmdodCBpbmZvIGlzIG5vdCBhdmFpbGFibGUgYW5kIHJlbW92ZSB0aGVtIGZyb20gdGhlIGRhdGEKICAgIGdlbmVzLm5vX2xlbmd0aCA8LSByb3duYW1lcyhkYXRhKVsgcm93bmFtZXMoZGF0YSkgJSFpbiUgbmFtZXMoZ2VuZS5sZW5ndGgpXQogICAgZGF0YSA8LSBkYXRhWyByb3duYW1lcyhkYXRhKSAlIWluJSBnZW5lcy5ub19sZW5ndGgsIF0KICAgIAogICAgIyMjIyMgQ3JlYXRlIEVkZ2VSIERHRUxpc3Qgb2JqZWN0CiAgICB5W1tncm91cF1dIDwtIGVkZ2VSOjpER0VMaXN0KGNvdW50cz1kYXRhLCAgZ3JvdXA9dGFyZ2V0JERhdGFzZXQpCiAgICAKICAgICMjIyMjIENvbnZlcnQgZGF0YSBpbnRvIFJQS00KICAgIHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQgPC0gZWRnZVI6OnJwa20oeVtbZ3JvdXBdXSwgZ2VuZS5sZW5ndGggPSBnZW5lLmxlbmd0aCwgbm9ybWFsaXplZC5saWIuc2l6ZXM9RkFMU0UsIGxvZz1GQUxTRSkKICAgIAogICAgIyMjIyMgLi4uIGFuZCB0aGVuIHRvIFRQTSBzY2FsZS4gQWRkIHNtYWxsIG9mZnNldCB0byBlYWNoIG9ic2VydmF0aW9uIHRvIGF2b2lkIHRha2luZyBsb2cgb2YgemVybwogICAgaWYgKCBwYXJhbXMkbG9nICkgewogICAgICB5W1tncm91cF1dJHRyYW5zZm9ybWVkIDwtIGxvZzIodHBtX2Zyb21fcnBrbSh5W1tncm91cF1dJHRyYW5zZm9ybWVkKzAuMjUpKQogICAgICAKICAgICAgIyMjIyMgS2VlcCBnZW5lcyB3aXRoIFRQTSBvZiBhdCBsZWFzdCAxIGluIG1vcmUgdGhhbiAxMCUgb2Ygc2FtcGxlcwogICAgICBmaWx0ZXIudGhyZXNob2xkIDwtIDErMC4yNQogICAgICBrZWVwIDwtIHJvd1N1bXMoeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCA+IGZpbHRlci50aHJlc2hvbGQpID49IG5jb2woeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCkvMTAKICAgICAgCiAgICAgICMjIyMjIE5vdGUgd2hpY2ggZ2VuZXMgb2YgaW50ZXJlc3QgYXJlIG5vdCBleHByZXNzZWQKICAgICAgZ2VuZXMya2VlcCRFWFBbIHJvd25hbWVzKGdlbmVzMmtlZXApICUhaW4lIG5hbWVzKGtlZXApIF0gPC0gRkFMU0UKICAgIAogICAgICAjIyMjIyBLZWVwIHRoZSBnZW5lcyBvZiBpbnRlcmVzdCB0b28KICAgICAga2VlcFsgbmFtZXMoa2VlcCkgJWluJSByb3duYW1lcyhnZW5lczJrZWVwKSBdIDwtIFRSVUUKICAgICAgeVtbZ3JvdXBdXSRmaWx0ZXJlZCA8LSB5W1tncm91cF1dJGNvdW50c1trZWVwLCBdCiAgICAgIHlbW2dyb3VwXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQgPC0geVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZFtrZWVwLCBdCiAgIAogICAgfSBlbHNlIHsKICAgICAgeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCA8LSB0cG1fZnJvbV9ycGttKHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQpCiAgICAgIAogICAgICAjIyMjIyBLZWVwIGdlbmVzIHdpdGggVFBNIG9mIGF0IGxlYXN0IDEgaW4gbW9yZSB0aGFuIDEwJSBvZiBzYW1wbGVzCiAgICAgIGZpbHRlci50aHJlc2hvbGQgPC0gMQogICAgICBrZWVwIDwtIHJvd1N1bXMoeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCA+IGZpbHRlci50aHJlc2hvbGQpID49IG5jb2woeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCkvMTAKICAgICAgCiAgICAgICMjIyMjIE5vdGUgd2hpY2ggZ2VuZXMgb2YgaW50ZXJlc3QgYXJlIG5vdCBleHByZXNzZWQKICAgICAgZ2VuZXMya2VlcCRFWFBbIHJvd25hbWVzKGdlbmVzMmtlZXApICUhaW4lIG5hbWVzKGtlZXApIF0gPC0gRkFMU0UKICAgIAogICAgICAjIyMjIyBLZWVwIHRoZSBnZW5lcyBvZiBpbnRlcmVzdCB0b28KICAgICAga2VlcFsgbmFtZXMoa2VlcCkgJWluJSByb3duYW1lcyhnZW5lczJrZWVwKSBdIDwtIFRSVUUKICAgICAgeVtbZ3JvdXBdXSRmaWx0ZXJlZCA8LSB5W1tncm91cF1dJGNvdW50c1trZWVwLCBdCiAgICAgIHlbW2dyb3VwXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQgPC0geVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZFtrZWVwLCBdCiAgICB9CiAgCiAgIyMjIyMgVFBNIHRyYW5zZm9ybWF0aW9uIHdpdGhvdXQgZmlsdGVyaW5nCiAgfSBlbHNlIGlmICggIXBhcmFtcyRmaWx0ZXIgJiYgcGFyYW1zJHRyYW5zZm9ybSA9PSAiVFBNIiApIHsKICAgIAogICAgIyMjIyMgR2V0IGdlbmVzIGxlbmd0aHMKICAgIGVkYiA8LSBldmFsKHBhcnNlKHRleHQgPSBwYXN0ZTAoIkVuc0RiLkhzYXBpZW5zLnYiLCBwYXJhbXMkZW5zZW1ibF92ZXJzaW9uKSkpCiAgICBnZW5lLmxlbmd0aCA8LSBsZW5ndGhPZihlZGIsIGZpbHRlciA9IEdlbmVJZEZpbHRlcihyb3duYW1lcyhkYXRhKSkpCiAgICAKICAgICMjIyMjIENoZWNrIGZvciB3aGljaCBnZW5lcyB0aGUgbGVuZ2h0IGluZm8gaXMgbm90IGF2YWlsYWJsZSBhbmQgcmVtb3ZlIHRoZW0gZnJvbSB0aGUgZGF0YQogICAgZ2VuZXMubm9fbGVuZ3RoIDwtIHJvd25hbWVzKGRhdGEpWyByb3duYW1lcyhkYXRhKSAlIWluJSBuYW1lcyhnZW5lLmxlbmd0aCldCiAgICBkYXRhIDwtIGRhdGFbIHJvd25hbWVzKGRhdGEpICUhaW4lIGdlbmVzLm5vX2xlbmd0aCwgXQogICAgCiAgICAjIyMjIyBDcmVhdGUgRWRnZVIgREdFTGlzdCBvYmplY3QKICAgIHlbW2dyb3VwXV0gPC0gZWRnZVI6OkRHRUxpc3QoY291bnRzPWRhdGEsICBncm91cD10YXJnZXQkRGF0YXNldCkKICAgIAogICAgIyMjIyMgQ29udmVydCBkYXRhIGludG8gUlBLTQogICAgeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCA8LSBlZGdlUjo6cnBrbSh5W1tncm91cF1dLCBnZW5lLmxlbmd0aCA9IGdlbmUubGVuZ3RoLCBub3JtYWxpemVkLmxpYi5zaXplcz1GQUxTRSwgbG9nPUZBTFNFKQogICAgCiAgICAjIyMjIyAuLi4gYW5kIHRoZW4gdG8gVFBNIHNjYWxlLiBBZGQgc21hbGwgb2Zmc2V0IHRvIGVhY2ggb2JzZXJ2YXRpb24gdG8gYXZvaWQgdGFraW5nIGxvZyBvZiB6ZXJvCiAgICBpZiAoIHBhcmFtcyRsb2cgKSB7CiAgICAgIHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQgPC0gbG9nMih0cG1fZnJvbV9ycGttKHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQrMC4yNSkpCiAgICB9IGVsc2UgewogICAgICB5W1tncm91cF1dJHRyYW5zZm9ybWVkIDwtIHRwbV9mcm9tX3Jwa20oeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCkKICAgIH0KICB9Cn0KCiMjIyMjIE5vdyBjb21iaW5lIERHRUxpc3Qgb2JqZWN0cyBjcmVhdGVkIGZvciBlYWNoIGdyb3VwCnlbWyJjb21iIl1dJHRyYW5zZm9ybWVkIDwtIGNiaW5kKHlbW3RhcmdldHNfbW9kLmxpc3RbMV1dXSR0cmFuc2Zvcm1lZCwgeVtbdGFyZ2V0c19tb2QubGlzdFsyXV1dJHRyYW5zZm9ybWVkKQp5W1siY29tYiJdXSRzYW1wbGVzIDwtIHJiaW5kKHlbW3RhcmdldHNfbW9kLmxpc3RbMV1dXSRzYW1wbGVzLCB5W1t0YXJnZXRzX21vZC5saXN0WzJdXV0kc2FtcGxlcykKCmlmICggcGFyYW1zJGZpbHRlciApIHsKICAKICAjIyMjIyBLZWVwIG9ubHkgZ2VuZXMgcHJlc2VudCBpbiBhbGwgc2V0cwogIGdlbmVzX21vZCA8LSBpbnRlcnNlY3Qocm93bmFtZXMoeVtbdGFyZ2V0c19tb2QubGlzdFsxXV1dJGZpbHRlcmVkKSwgcm93bmFtZXMoeVtbdGFyZ2V0c19tb2QubGlzdFsyXV1dJGZpbHRlcmVkKSkKICB5W1t0YXJnZXRzX21vZC5saXN0WzFdXV0kZmlsdGVyZWQgPC0geVtbdGFyZ2V0c19tb2QubGlzdFsxXV1dJGZpbHRlcmVkWyByb3duYW1lcyh5W1t0YXJnZXRzX21vZC5saXN0WzFdXV0kZmlsdGVyZWQpICVpbiUgZ2VuZXNfbW9kLCBdCiAgeVtbdGFyZ2V0c19tb2QubGlzdFsyXV1dJGZpbHRlcmVkIDwtIHlbW3RhcmdldHNfbW9kLmxpc3RbMl1dXSRmaWx0ZXJlZFsgcm93bmFtZXMoeVtbdGFyZ2V0c19tb2QubGlzdFsyXV1dJGZpbHRlcmVkKSAlaW4lIGdlbmVzX21vZCwgXQogIHlbW3RhcmdldHNfbW9kLmxpc3RbMV1dXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCA8LSB5W1t0YXJnZXRzX21vZC5saXN0WzFdXV0kZmlsdGVyZWQudHJhbnNmb3JtZWRbIHJvd25hbWVzKHlbW3RhcmdldHNfbW9kLmxpc3RbMV1dXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCkgJWluJSBnZW5lc19tb2QsIF0KICB5W1t0YXJnZXRzX21vZC5saXN0WzJdXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQgPC0geVtbdGFyZ2V0c19tb2QubGlzdFsyXV1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkWyByb3duYW1lcyh5W1t0YXJnZXRzX21vZC5saXN0WzJdXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQpICVpbiUgZ2VuZXNfbW9kLCBdCiAKICB5W1siY29tYiJdXSRmaWx0ZXJlZCA8LSBjYmluZCh5W1t0YXJnZXRzX21vZC5saXN0WzFdXV0kZmlsdGVyZWQsIHlbW3RhcmdldHNfbW9kLmxpc3RbMl1dXSRmaWx0ZXJlZCkKICB5W1siY29tYiJdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCA8LSBjYmluZCh5W1t0YXJnZXRzX21vZC5saXN0WzFdXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQsIHlbW3RhcmdldHNfbW9kLmxpc3RbMl1dXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCkKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKHRhcmdldCwgdGFyZ2V0X21vZCwgZ2VuZXNfbW9kLCBrZWVwKQpgYGAKCmBgYHtyIGRhdGFfdHJhbnNmb3JtYXRpb25fcGxvdCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDYsIGZpZy5zaG93PSJoaWRlIn0KIyMjIyMgQXNzaWduIGNvbG91cnMgdG8gdGFyZ2V0cyBhbmQgZGF0YXNldHMKdGFyZ2V0IDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQp0YXJnZXRzLmNvbG91ciA8LSBnZXRDb2xvdXJzKHRhcmdldCRUYXJnZXQpCiAgCiMjIyMjIENvbGxlY3QgdGhlIG1vc3QgZXh0cmVtZSBkZW5zaXR5IHZhbHVlcyBmb3Igc2V0IHRoZSB4LWF4aXMgYW5kIHktYXhpcyBib3VuZGFyaWVzCmRlbi54IDwtIGRlbnNpdHkoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLDFdKSR4CmRlbi55IDwtIGRlbnNpdHkoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLDFdKSR5CiAgCmZvciAoaSBpbiAyOm5jb2woeVtbImNvbWIiXV0kdHJhbnNmb3JtZWQpKSB7CiAgZGVuIDwtIGRlbnNpdHkoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLGldKQogIGRlbi54IDwtIHNvcnQoYyhkZW4ueCwgZGVuJHgpKQogIGRlbi55IDwtIHNvcnQoYyhkZW4ueSwgZGVuJHkpKQp9CgojIyMjIyBQbG90IHJlYWQgY291bnRzIGFnYWluc3QgdHJhbnNmb3JtZWQgZGF0YQppZiAoIHBhcmFtcyRmaWx0ZXIgKSB7CiAgc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCiAgCiAgIyMjIyMgT3JnYW5pc2UgdGhlIGRhdGEgaW50byBkYXRhIGZyYW1lCiAgaWYgKCBwYXJhbXMkbG9nICkgewogICAgZGF0YS5kZiA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKCBleHAoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLG5jb2woeVtbImNvbWIiXV0kdHJhbnNmb3JtZWQpXSksIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGEiXV1bLG5jb2wocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSldKSkKICAgIG5hbWVzKGRhdGEuZGYpIDwtIGMoIlRyYW5zZm9ybWVkIiwgIkNvdW50cyIpCiAgICBkYXRhLmRmJFRyYW5zZm9ybWVkIDwtIGxvZyhkYXRhLmRmJFRyYW5zZm9ybWVkKQogICAgCiAgfSBlbHNlIHsKICAgICBkYXRhLmRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoIHlbWyJjb21iIl1dJHRyYW5zZm9ybWVkWyxuY29sKHlbWyJjb21iIl1dJHRyYW5zZm9ybWVkKV0sIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGEiXV1bLG5jb2wocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSldKSkKICAgIG5hbWVzKGRhdGEuZGYpIDwtIGMoIlRyYW5zZm9ybWVkIiwgIkNvdW50cyIpCiAgfQogIAogICMjIyMjIEtlZXAgb25seSBnZW5lcyB3aXRoIHJlYWQgY291bnRzIGJlbG93IHRoZSA5OXRoIHBlcmNlbnRpbGUKICBkYXRhLmRmIDwtIGRhdGEuZGZbIGRhdGEuZGYkQ291bnRzIDwgcXVhbnRpbGUoZGF0YS5kZiRDb3VudHMsIDAuOTkpLCBdCiAgCiAgIyMjIyMgS2VlcCBvbmx5IGV2ZXJ5IDI1dGggZ2VuZXMgdG8gcmVkdWNlIHRoZSBzaXplIG9mIHRoZSBwbG90CiAgZGF0YS5kZiA8LSBkYXRhLmRmWyBzZXEoMSxucm93KGRhdGEuZGYpLCBieT0yNSksIF0KICAKICAjIyMjIyBHZW5lcmF0ZSBwbG90IGZvciBmaWx0ZXJlZCBkYXRhCiAgY291bnRzX3ZzX3RyYW5zZm9ybWVkIDwtIHBsb3RfbHkoIGRhdGEuZGYsIHggPSB+VHJhbnNmb3JtZWQsIHkgPSB+Q291bnRzLCB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gMzAwLCBjb2xvciA9IEkoJ2JsYWNrJyksIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IDUpLCB0eXBlPSJzY2F0dGVyIiwgbW9kZSA9ICJtYXJrZXJzIiwgbmFtZSA9IHBhc3RlMChwYXJhbXMkdHJhbnNmb3JtLCAiIC8gQ291bnRzIChQYXRpZW50KSIpICkgJT4lIAogICAgYWRkX3RyYWNlKHggPSBjKGZpbHRlci50aHJlc2hvbGQsIGZpbHRlci50aHJlc2hvbGQpLCB5PSBjKDAsIG1heChkYXRhLmRmJENvdW50cykpLCBtb2RlID0gImxpbmVzIiwgY29sb3IgPSBJKCJyZWQiKSwgbmFtZSA9ICJGaWx0ZXJpbmcgdGhyZXNob2xkIikgJT4lCiAgICAKICAgIGxheW91dCh0aXRsZSA9ICIiLCB4YXhpcyA9IGxpc3QodGl0bGUgPSBwYXN0ZTAocGFyYW1zJHRyYW5zZm9ybSwgInMiKSksIHlheGlzID0gbGlzdCh0aXRsZSA9ICJDb3VudHMiKSwgc2hvd2xlZ2VuZD1UUlVFKQogIAogICMjIyMjIFNhdmUgaW50ZXJhY3RpdmUgcGxvdCBhcyBodG1sIGZpbGUKICBzYXZlV2lkZ2V0Rml4KGNvdW50c192c190cmFuc2Zvcm1lZCwgZmlsZSA9IHBhc3RlKFBsb3RzRGlyLCAiY291bnRzX3ZzX3RyYW5zZm9ybWVkLmh0bWwiLCBzZXAgPSAiLyIpKQoKICAjIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwogIGRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCiAgCiAgaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyX2dyb3VwKSApIHsKICAgIGxlZ2VuZCA8LSBjKGV4dF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXJfZ3JvdXAsICJQYXRpZW50IikKICB9IGVsc2UgewogICAgbGVnZW5kIDwtIGMoZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlcl9ncm91cCwgIlBhdGllbnQiKQogIH0KICAKICAjIyMjIyBCZWZvcmUgZmlsdGVyaW5nCiAgcGFyKG1mcm93PWMoMSwyKSkKICBwbG90KGRlbnNpdHkoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLDFdKSwgbHdkPTIsIHhsaW09YyhkZW4ueFsxXSxtYXgoZGF0YS5kZiRUcmFuc2Zvcm1lZCkpLCB5bGltPWMoZGVuLnlbMV0sZGVuLnlbbGVuZ3RoKGRlbi55KV0pLCBsYXM9MiwgbWFpbj0iIiwgeGxhYj0iIiwgY29sPXRhcmdldHMuY29sb3VyW1syXV1bMV0pCiAgdGl0bGUobWFpbj0iVHJhbnNmb3JtZWQgZGF0YSAodW5maWx0ZXJlZCkiLCB4bGFiPXBhcmFtcyR0cmFuc2Zvcm0pCiAgYWJsaW5lKHY9MCwgbHR5PTMpCiAgCiAgZm9yIChpIGluIDI6bmNvbCh5W1siY29tYiJdXSR0cmFuc2Zvcm1lZCkpewogICAgZGVuIDwtIGRlbnNpdHkoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLGldKQogICAgbGluZXMoZGVuJHgsIGRlbiR5LCBsd2Q9MiwgY29sPXRhcmdldHMuY29sb3VyW1syXV1baV0pCiAgfQogIGxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9bGVnZW5kLCBmaWxsPXRhcmdldHMuY29sb3VyW1sxXV0sIGJ0eT0ibiIsIGJnID0gInRyYW5zcGFyZW50IikKICAKICBkYXRhX3RyYW5zZm9ybWF0aW9uX25vbmZpbHRlcmVkIDwtIHJlY29yZFBsb3QoKQogIAogICMjIyMjIEFmdGVyIGZpbHRlcmluZwogIHBsb3QoZGVuc2l0eSh5W1siY29tYiJdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZFssMV0pLCBsd2Q9MiwgeGxpbT1jKGRlbi54WzFdLG1heChkYXRhLmRmJFRyYW5zZm9ybWVkKSksIHlsaW09YyhkZW4ueVsxXSxkZW4ueVtsZW5ndGgoZGVuLnkpXSksIGxhcz0yLCBtYWluPSIiLCB4bGFiPSIiLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXVsxXSkKICB0aXRsZShtYWluPSJUcmFuc2Zvcm1lZCBhbmQgZmlsdGVyZWQgZGF0YSIsIHhsYWI9cGFyYW1zJHRyYW5zZm9ybSkKICBhYmxpbmUodj0wLCBsdHk9MykKICAKICBmb3IgKGkgaW4gMjpuY29sKHlbWyJjb21iIl1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkKSl7CiAgICBkZW4gPC0gZGVuc2l0eSh5W1siY29tYiJdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZFssaV0pCiAgICBsaW5lcyhkZW4keCwgZGVuJHksIGx3ZD0yLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXVtpXSkKICB9CiAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZWdlbmQsIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgYnR5PSJuIiwgYmcgPSAidHJhbnNwYXJlbnQiKQogIAogIGRhdGFfdHJhbnNmb3JtYXRpb25fZmlsdGVyZWQgPC0gcmVjb3JkUGxvdCgpCiAgCiAgIyMjIyMgU2F2ZSB0aGUgcGxvdCBhcyBwbmcgZmlsZQogIHBuZyhwYXN0ZTAoUGxvdHNEaXIsICIvZmlsdGVyaW5nLnBuZyIpLCB3aWR0aD05MDAsIGhlaWdodD00MDAsIHBvaW50c2l6ZSA9IDE0KQogIHBhcihtZnJvdz1jKDEsMikpCiAgCiAgIyMjIyMgQmVmb3JlIGZpbHRlcmluZwogIHBsb3QoZGVuc2l0eSh5W1siY29tYiJdXSR0cmFuc2Zvcm1lZFssMV0pLCBsd2Q9MiwgeGxpbT1jKGRlbi54WzFdLGRlbi54W2xlbmd0aChkZW4ueCldKSwgeWxpbT1jKGRlbi55WzFdLGRlbi55W2xlbmd0aChkZW4ueSldKSwgbGFzPTIsIG1haW49IiIsIHhsYWI9IiIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dWzFdKQogIHRpdGxlKG1haW49IlRyYW5zZm9ybWVkIGRhdGEgKHVuZmlsdGVyZWQpIiwgeGxhYj1wYXJhbXMkdHJhbnNmb3JtKQogIGFibGluZSh2PTAsIGx0eT0zKQogIAogIGZvciAoaSBpbiAyOm5jb2woeVtbImNvbWIiXV0kdHJhbnNmb3JtZWQpKXsKICAgIGRlbiA8LSBkZW5zaXR5KHlbWyJjb21iIl1dJHRyYW5zZm9ybWVkWyxpXSkKICAgIGxpbmVzKGRlbiR4LCBkZW4keSwgbHdkPTIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dW2ldKQogIH0KICBsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxlZ2VuZCwgZmlsbD10YXJnZXRzLmNvbG91cltbMV1dLCBjZXggPSAwLjcsIGJ0eT0ibiIsIGJnID0gInRyYW5zcGFyZW50IikKICAKICAjIyMjIyBBZnRlciBmaWx0ZXJpbmcKICBwbG90KGRlbnNpdHkoeVtbImNvbWIiXV0kZmlsdGVyZWQudHJhbnNmb3JtZWRbLDFdKSwgbHdkPTIsIHhsaW09YyhkZW4ueFsxXSxkZW4ueFtsZW5ndGgoZGVuLngpXSksIHlsaW09YyhkZW4ueVsxXSxkZW4ueVtsZW5ndGgoZGVuLnkpXSksIGxhcz0yLCBtYWluPSIiLCB4bGFiPSIiLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXVsxXSkKICB0aXRsZShtYWluPSJUcmFuc2Zvcm1lZCBhbmQgZmlsdGVyZWQgZGF0YSIsIHhsYWI9cGFyYW1zJHRyYW5zZm9ybSkKICBhYmxpbmUodj0wLCBsdHk9MykKICAKICBmb3IgKGkgaW4gMjpuY29sKHlbWyJjb21iIl1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkKSl7CiAgICBkZW4gPC0gZGVuc2l0eSh5W1siY29tYiJdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZFssaV0pCiAgICBsaW5lcyhkZW4keCwgZGVuJHksIGx3ZD0yLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXVtpXSkKICB9CiAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZWdlbmQsIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgY2V4ID0gMC43LCBidHk9Im4iLCBiZyA9ICJ0cmFuc3BhcmVudCIpCiAgaW52aXNpYmxlKGRldi5vZmYoKSkKICAKIyMjIyMgV2l0aG91dCBmaWx0ZXJpbmcKfSBlbHNlIHsKICBwbG90KGRlbnNpdHkoeVtbImNvbWIiXV0kdHJhbnNmb3JtZWRbLDFdKSwgbHdkPTIsIHhsaW09YyhkZW4ueFsxXSxkZW4ueFtsZW5ndGgoZGVuLngpXSksIHlsaW09YyhkZW4ueVsxXSxkZW4ueVtsZW5ndGgoZGVuLnkpXSksIGxhcz0yLCBtYWluPSIiLCB4bGFiPSIiLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXVsxXSkKICB0aXRsZShtYWluPSJUcmFuc2Zvcm1lZCBkYXRhICh1bmZpbHRlcmVkKSIsIHhsYWI9cGFyYW1zJHRyYW5zZm9ybSkKICBhYmxpbmUodj0wLCBsdHk9MykKICAKICBmb3IgKGkgaW4gMjpuY29sKHlbWyJjb21iIl1dJHRyYW5zZm9ybWVkKSl7CiAgICBkZW4gPC0gZGVuc2l0eSh5W1siY29tYiJdXSR0cmFuc2Zvcm1lZFssaV0pCiAgICBsaW5lcyhkZW4keCwgZGVuJHksIGx3ZD0yLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXVtpXSkKICB9CiAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZWdlbmQsIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgYnR5PSJuIiwgYmcgPSAidHJhbnNwYXJlbnQiKQogIAogIGRhdGFfdHJhbnNmb3JtYXRpb25fbm9uZmlsdGVyZWQgPC0gcmVjb3JkUGxvdCgpCiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCiAgCiAgIyMjIyMgU2F2ZSB0aGUgcGxvdCBhcyBwbmcgZmlsZQogIHBuZyhwYXN0ZTAoUGxvdHNEaXIsICIvZmlsdGVyaW5nLnBuZyIpLCB3aWR0aD05MDAsIGhlaWdodD00MDAsIHBvaW50c2l6ZSA9IDE0KQogIHBsb3QoZGVuc2l0eSh5W1siY29tYiJdXSR0cmFuc2Zvcm1lZFssMV0pLCBsd2Q9MiwgeGxpbT1jKGRlbi54WzFdLGRlbi54W2xlbmd0aChkZW4ueCldKSwgeWxpbT1jKGRlbi55WzFdLGRlbi55W2xlbmd0aChkZW4ueSldKSwgbGFzPTIsIG1haW49IiIsIHhsYWI9IiIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dWzFdKQogIHRpdGxlKG1haW49IlRyYW5zZm9ybWVkIGRhdGEgKHVuZmlsdGVyZWQpIiwgeGxhYj1wYXJhbXMkdHJhbnNmb3JtKQogIGFibGluZSh2PTAsIGx0eT0zKQogIAogIGZvciAoaSBpbiAyOm5jb2woeVtbImNvbWIiXV0kdHJhbnNmb3JtZWQpKXsKICAgIGRlbiA8LSBkZW5zaXR5KHlbWyJjb21iIl1dJHRyYW5zZm9ybWVkWyxpXSkKICAgIGxpbmVzKGRlbiR4LCBkZW4keSwgbHdkPTIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dW2ldKQogIH0KICBsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxlZ2VuZCwgZmlsbD10YXJnZXRzLmNvbG91cltbMV1dLCBjZXggPSAwLjcsIGJ0eT0ibiIsIGJnID0gInRyYW5zcGFyZW50IikKICBpbnZpc2libGUoZGV2Lm9mZigpKQp9CgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0oZGF0YSwgZGF0YS5kZiwgdGFyZ2V0LCBkZW4ueCwgZGVuLnkpCmBgYAoKYGBge3IgZGF0YV9ub3JtYWxpc2F0aW9uLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UgfQojIyMjIyBEdXJpbmcgdGhlIHNhbXBsZSBwcmVwYXJhdGlvbiBvciBzZXF1ZW5jaW5nIHByb2Nlc3MsIGV4dGVybmFsIGZhY3RvcnMgdGhhdCBhcmUgbm90IG9mIGJpb2xvZ2ljYWwgaW50ZXJlc3QgY2FuIGFmZmVjdCB0aGUgZXhwcmVzc2lvbiBvZiBpbmRpdmlkdWFsIHNhbXBsZXMuIEZvciBleGFtcGxlLCBzYW1wbGVzIHByb2Nlc3NlZCBpbiB0aGUgZmlyc3QgYmF0Y2ggb2YgYW4gZXhwZXJpbWVudCBjYW4gaGF2ZSBoaWdoZXIgZXhwcmVzc2lvbiBvdmVyYWxsIHdoZW4gY29tcGFyZWQgdG8gc2FtcGxlcyBwcm9jZXNzZWQgaW4gYSBzZWNvbmQgYmF0Y2guIEl0IGlzIGFzc3VtZWQgdGhhdCBhbGwgc2FtcGxlcyBzaG91bGQgaGF2ZSBhIHNpbWlsYXIgcmFuZ2UgYW5kIGRpc3RyaWJ1dGlvbiBvZiBleHByZXNzaW9uIHZhbHVlcy4gTm9ybWFsaXNhdGlvbiBmb3Igc2FtcGxlLXNwZWNpZmljIGVmZmVjdHMgaXMgcmVxdWlyZWQgdG8gZW5zdXJlIHRoYXQgdGhlIGV4cHJlc3Npb24gZGlzdHJpYnV0aW9ucyBvZiBlYWNoIHNhbXBsZSBhcmUgc2ltaWxhciBhY3Jvc3MgdGhlIGVudGlyZSBleHBlcmltZW50LgoKIyMjIyMgVE1NIG5vcm1hbHNhdGlvbi4gVHJpbW1lZCBtZWFuIG9mIE0tdmFsdWVzIChodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8yMDE5Njg2NykgKFRNTSkgaXMgcGVyZm9ybWVkIHVzaW5nIHRoZSBjYWxjTm9ybUZhY3RvcnMgZnVuY3Rpb24gaW4gZWRnZVIuIFRoZSBub3JtYWxpc2F0aW9uIGZhY3RvcnMgY2FsY3VsYXRlZCBoZXJlIGFyZSB1c2VkIGFzIGEgc2NhbGluZyBmYWN0b3IgZm9yIHRoZSBsaWJyYXJ5IHNpemVzLiBUTU0gaXMgdGhlIHJlY29tbWVuZGVkIGZvciBtb3N0IFJOQS1TZXEgZGF0YSB3aGVyZSB0aGUgbWFqb3JpdHkgKG1vcmUgdGhhbiBoYWxmKSBvZiB0aGUgZ2VuZXMgYXJlIGJlbGlldmVkIG5vdCBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgYmV0d2VlbiBhbnkgcGFpciBvZiB0aGUgc2FtcGxlcy4gSXQgYWRqdXN0cyBmb3IgUk5BIGNvbXBvc2l0aW9uIGVmZmVjdCwgY2FsY3VsYXRlcyBzY2FsaW5nIGZhY3RvcnMgZm9yIHRoZSBsaWJyYXJ5IHNpemVzIHdpdGggY2FsY05vcm1GYWN0b3JzIGZ1bmN0aW9uIHVzaW5nIHRyaW1tZWQgbWVhbiBvZiBNLXZhbHVlcyAoVE1NKSBiZXR3ZWVuIGVhY2ggcGFpciBvZiBzYW1wbGVzLiBOb3RlLCB0aGF0IHRoZSByYXcgcmVhZCBjb3VudHMgYXJlIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBub3JtYWxpc2F0aW9uIGZhY3RvcnMKICAKIyMjIyBGb3IgZWFjaCBncm91cC4uLgpmb3IgKCBncm91cCBpbiB0YXJnZXRzX21vZC5saXN0ICkgewogIGlmICggcGFyYW1zJHRyYW5zZm9ybSA9PSAiQ1BNIiApIHsKICAgIAogICAgIyMjIyMgQ2FsY3VsYXRlIG5vcm1hbGl6YXRpb24gZmFjdG9ycyBhbmQgdHJhbnNmb3JtYXRpb25zIGZyb20gdGhlIHJhdy1zY2FsZSB0byBDUE0gYW5kIG5vcm1hbGlzYXRpb24gdXNpbmcgdXNlci1kZWZpbmVkIG1ldGhvZAogICAgaWYgKCBwYXJhbXMkZmlsdGVyICkgewogICAgICB5W1tncm91cF1dJG5vTm9ybSA8LSB5W1tncm91cF1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkCiAgICAgIHlbW2dyb3VwXV0kZmlsdGVyZWQkc2FtcGxlc1sibm9ybS5mYWN0b3JzIl0gPC0gZWRnZVI6OmNhbGNOb3JtRmFjdG9ycyh5W1tncm91cF1dJGZpbHRlcmVkLCBtZXRob2QgPSBwYXJhbXMkbm9ybSkkc2FtcGxlc1sibm9ybS5mYWN0b3JzIl0KICAgICAgeVtbZ3JvdXBdXSRub3JtIDwtIGVkZ2VSOjpjcG0oeVtbZ3JvdXBdXSRmaWx0ZXJlZCwgbm9ybWFsaXplZC5saWIuc2l6ZXM9VFJVRSwgbG9nPXBhcmFtcyRsb2csIHByaW9yLmNvdW50PTAuMjUpCiAgICAKICAgIH0gZWxzZSB7CiAgICAgIHlbW2dyb3VwXV0kbm9Ob3JtIDwtIHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQKICAgICAgeVtbZ3JvdXBdXSRzYW1wbGVzWyJub3JtLmZhY3RvcnMiXSA8LSBlZGdlUjo6Y2FsY05vcm1GYWN0b3JzKHlbW2dyb3VwXV0sIG1ldGhvZCA9IHBhcmFtcyRub3JtKSRzYW1wbGVzWyJub3JtLmZhY3RvcnMiXQogICAgICB5W1tncm91cF1dJG5vcm0gPC0gZWRnZVI6OmNwbSh5W1tncm91cF1dLCBub3JtYWxpemVkLmxpYi5zaXplcz1UUlVFLCBsb2c9cGFyYW1zJGxvZywgcHJpb3IuY291bnQ9MC4yNSkKICAgIH0KICAgIAogICMjIyMjIFF1YW50aWxlIG5vcm1hbHNhdGlvbiAoZnJvbSBodHRwczovL3d3dy5iaW9zdGFycy5vcmcvcC8yOTY5OTIvICkKICB9IGVsc2UgaWYgKCBwYXJhbXMkdHJhbnNmb3JtID09ICJUUE0iICkgewogICAgCiAgICAjIyMjIyBOb3JtYWxpc2F0aW9uIHVzaW5nIHF1YW50aWxlIG1ldGhvZAogICAgaWYgKCBwYXJhbXMkZmlsdGVyICkgewogICAgICB5W1tncm91cF1dJG5vTm9ybSA8LSB5W1tncm91cF1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkCiAgICAgIHlbW2dyb3VwXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQgPC0gZGF0YS5tYXRyaXgoeVtbZ3JvdXBdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCkgCiAgICAgIAogICAgICBpZiAoIHRvbG93ZXIocGFyYW1zJG5vcm0pICE9ICJub25lIiApIHsKICAgICAgICB5W1tncm91cF1dJG5vcm0gIDwtIG5vcm1hbGl6ZS5xdWFudGlsZXMoeVtbZ3JvdXBdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCwgY29weSA9IFRSVUUpCiAgICAgICAgY29sbmFtZXMoeVtbZ3JvdXBdXSRub3JtKSA8LSBjb2xuYW1lcyh5W1tncm91cF1dJGZpbHRlcmVkLnRyYW5zZm9ybWVkKQogICAgICAgIHJvd25hbWVzKHlbW2dyb3VwXV0kbm9ybSkgPC0gcm93bmFtZXMoeVtbZ3JvdXBdXSRmaWx0ZXJlZC50cmFuc2Zvcm1lZCkKICAgICAgfSBlbHNlIHsKICAgICAgICB5W1tncm91cF1dJG5vcm0gIDwtIHlbW2dyb3VwXV0kZmlsdGVyZWQudHJhbnNmb3JtZWQKICAgICAgfQogICAgfSBlbHNlIHsKICAgICAgeVtbZ3JvdXBdXSRub05vcm0gPC0geVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZAogICAgICB5W1tncm91cF1dJHRyYW5zZm9ybWVkIDwtIGRhdGEubWF0cml4KHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQpCiAgICAgIAogICAgICBpZiAoIHRvbG93ZXIocGFyYW1zJG5vcm0pICE9ICJub25lIiApIHsKICAgICAgICB5W1tncm91cF1dJG5vcm0gIDwtIG5vcm1hbGl6ZS5xdWFudGlsZXMoeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCwgY29weSA9IFRSVUUpCiAgICAgICAgY29sbmFtZXMoeVtbZ3JvdXBdXSRub3JtKSA8LSBjb2xuYW1lcyh5W1tncm91cF1dJHRyYW5zZm9ybWVkKQogICAgICAgIHJvd25hbWVzKHlbW2dyb3VwXV0kbm9ybSkgPC0gcm93bmFtZXMoeVtbZ3JvdXBdXSR0cmFuc2Zvcm1lZCkKICAgICAgfSBlbHNlIHsKICAgICAgICB5W1tncm91cF1dJG5vcm0gIDwtIHlbW2dyb3VwXV0kdHJhbnNmb3JtZWQKICAgICAgfQogICAgfQogIH0KfSAgCgojIyMjIyBDb21iaW5lIERHRUxpc3Qgb2JqZWN0cyBjcmVhdGVkIGZvciBlYWNoIGdyb3VwCnlbWyJjb21iIl1dJG5vTm9ybSA8LSBjYmluZCh5W1t0YXJnZXRzX21vZC5saXN0WzFdXV0kbm9Ob3JtLCB5W1t0YXJnZXRzX21vZC5saXN0WzJdXV0kbm9Ob3JtKQp5W1siY29tYiJdXSRub3JtIDwtIGNiaW5kKHlbW3RhcmdldHNfbW9kLmxpc3RbMV1dXSRub3JtLCB5W1t0YXJnZXRzX21vZC5saXN0WzJdXV0kbm9ybSkKCmlmICggdG9sb3dlcihwYXJhbXMkbm9ybSkgIT0gIm5vbmUiICkgewogIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dIDwtIHlbWyJjb21iIl1dJG5vcm0KfSBlbHNlIHsKICByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXSA8LSB5W1siY29tYiJdXSRub05vcm0KfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKHRhcmdldHNfbW9kLmxpc3QpCmBgYAoKYGBge3IgZGF0YV9ub3JtYWxpc2F0aW9uX3Bsb3QsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA2LCBmaWcuc2hvdz0iaGlkZSJ9CiMjIyMjIFBsb3QgZXhwcmVzc2lvbiBkaXN0cmlidXRpb24gb2Ygc2FtcGxlcyBmb3IgdW5ub3JtYWxpc2VkIGFuZCBub3JtYWxpc2VkIGRhdGEKcGFyKG1mcm93PWMoMiwxKSwgbWFyPWMoMiwgNSwgMywgMikpCgojIyMjIyBVbm5vcm1hbGlzZWQgZGF0YQpib3hwbG90KHlbWyJjb21iIl1dJG5vTm9ybSwgbGFzPTIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dLCBtYWluPSIiLCBwY2g9IiIsIGxhcz0zLCB4YXh0PSJuIiwgb3V0bGluZSA9IEZBTFNFKQp0aXRsZShtYWluPSJVbm5vcm1hbGlzZWQgZGF0YSIsIHlsYWI9cGFyYW1zJHRyYW5zZm9ybSkKbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZWdlbmQsIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgaG9yaXo9VFJVRSwgYmcgPSAidHJhbnNwYXJlbnQiLCBib3guY29sPSJ0cmFuc3BhcmVudCIpCgpkYXRhX25vbm5vcm1hbGlzZWQgPC0gcmVjb3JkUGxvdCgpCgojIyMjIyBOb3JtYWxpc2VkIGRhdGEKYm94cGxvdCh5W1siY29tYiJdXSRub3JtLCBsYXM9MiwgY29sPXRhcmdldHMuY29sb3VyW1syXV0sIG1haW49IiIsIHBjaD0iIiwgbGFzPTMsIHhheHQ9Im4iLCBvdXRsaW5lID0gRkFMU0UpCnRpdGxlKG1haW49cGFzdGUwKCJOb3JtYWxpc2VkIGRhdGEgKCIsIHBhcmFtcyRub3JtLCAiKSIpLCB5bGFiPXBhcmFtcyR0cmFuc2Zvcm0pCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9bGVnZW5kLCBmaWxsPXRhcmdldHMuY29sb3VyW1sxXV0sIGhvcml6PVRSVUUsIGJnID0gInRyYW5zcGFyZW50IiwgYm94LmNvbD0idHJhbnNwYXJlbnQiKQoKZGF0YV9ub3JtYWxpc2VkIDwtIHJlY29yZFBsb3QoKQoKIyMjIyMgU2F2ZSB0aGUgcGxvdCBhcyBwbmcgZmlsZQpwbmcocGFzdGUwKFBsb3RzRGlyLCAiL25vcm1hbGlzYXRpb24ucG5nIiksIHdpZHRoPTkwMCwgaGVpZ2h0PTcwMCwgcG9pbnRzaXplID0gMTQpCnBhcihtZnJvdz1jKDIsMSksIG1hcj1jKDIsIDUsIDMsIDIpKQogIAojIyMjIyBVbm5vcm1hbGlzZWQgZGF0YQpib3hwbG90KHlbWyJjb21iIl1dJG5vTm9ybSwgbGFzPTIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dLCBtYWluPSIiLCBwY2g9IiIsIGxhcz0zLCB4YXh0PSJuIiwgb3V0bGluZSA9IEZBTFNFKQp0aXRsZShtYWluPSJVbm5vcm1hbGlzZWQgZGF0YSIsIHlsYWI9cGFyYW1zJHRyYW5zZm9ybSkKbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZWdlbmQsIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgaG9yaXo9VFJVRSwgYmcgPSAidHJhbnNwYXJlbnQiLCBjZXggPSAwLjcsIGJveC5jb2w9InRyYW5zcGFyZW50IikKICAKIyMjIyMgTm9ybWFsaXNlZCBkYXRhCmJveHBsb3QoeVtbImNvbWIiXV0kbm9ybSwgbGFzPTIsIGNvbD10YXJnZXRzLmNvbG91cltbMl1dLCBtYWluPSIiLCBwY2g9IiIsIGxhcz0zLCB4YXh0PSJuIiwgb3V0bGluZSA9IEZBTFNFKQp0aXRsZShtYWluPXBhc3RlMCgiTm9ybWFsaXNlZCBkYXRhICgiLCBwYXJhbXMkbm9ybSwgIikiKSwgeWxhYj1wYXJhbXMkdHJhbnNmb3JtKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxlZ2VuZCwgZmlsbD10YXJnZXRzLmNvbG91cltbMV1dLCBob3Jpej1UUlVFLCBiZyA9ICJ0cmFuc3BhcmVudCIsIGNleCA9IDAuNywgYm94LmNvbD0idHJhbnNwYXJlbnQiKQppbnZpc2libGUoZGV2Lm9mZigpKQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGRlbiwgeSkKYGBgCgpgYGB7ciBiYXRjaF9lZmZlY3RfY29ycmVjdGlvbiwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPXBhcmFtcyRiYXRjaF9ybX0KIyMjIyMgVGhlIHN0cmF0ZWd5IGZvciBjb3JyZWN0aW5nIGRhdGEgZm9yIGJhdGNoIGVmZmVjdHMgaXMgdG8gY29uc2lkZXIgdGhlIGludmVzdGlnYXRlZCBzYW1wbGUgYW5kIGludGVybmFsIHJlZmVyZW5jZSBjb2hvcnQgYXMgb25lIGdyb3VwIChiYXRjaCkgKHJlZ2FyZGxlc3Mgb2YgdGhlIGludmVzdGlnYXRlZCBwYXRpZW50IHRpc3N1ZSBvcmlnaW4pLCBhbmQgVENHQSBkYXRhIChvZiBhbnkgY2FuY2VyIHR5cGUpIGFzIGFub3RoZXIgYmF0Y2guIFRoZSBvYmplY3RpdmUgaXMgdG8gcmVtb3ZlIGFzIG11Y2ggYXMgcG9zc2libGUgZGF0YSB2YXJpYXRpb24gZHVlIHRvIHRlY2huaWNhbCBmYWN0b3JzLgpiYXRjaGVzIDwtIGFzLmNoYXJhY3RlcihyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJzYW1wbGVfYW5ub3QiXV0kRGF0YXNldCkKCiMjIyMjIENoYW5nZSB0aGUgc2FtcGxlIGRhdGFzZXQgbmFtZSB0byBpbnRlcm5hbCByZWZlcmVuY2UgY29ob3J0CmJhdGNoZXNbIG1hdGNoKHNhbXBsZV9uYW1lLCBiYXRjaGVzKSBdIDwtIGludF9jYW5jZXJfZ3JvdXAKCiMjIyMjIFBlcmZvcm0gYmF0Y2gtZWZmZWN0IGNvcnJlY3RyaW9uIHVzaW5nIGxpbW1hCnJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImJhdGNoX2VmZmVjdF9jb3JyZWN0ZWQiXV0gPC0gbGltbWE6OnJlbW92ZUJhdGNoRWZmZWN0KHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dLCBiYXRjaCA9IGJhdGNoZXMpCmBgYAoKYGBge3IgcGNhLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQoKIyMjIyMgUGVyZm9ybSBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIChQQ0EpIHVzaW5nIGNvbWJpbmVkLW9ubHkgZGF0YSBhbmQgYmF0Y2gtZWZmZWN0IGNvcnJlY3RlZCBkYXRhCiMjIyMjIExvb3AgdGhyb3VnaCBjb21iaW5lZCBkYXRhc2V0cyBhbmQgcGVyZm9ybSBQQ0EKZm9yICggZGF0YXNldCBpbiBuYW1lcyhyZWZfZGF0YXNldC5saXN0KSApIHsKICB0YXJnZXQgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sic2FtcGxlX2Fubm90Il1dCiAgdGFyZ2V0JERhdGFzZXQgPC0gZ3N1YihzYW1wbGVfbmFtZSwgIlBhdGllbnQiLCB0YXJnZXQkRGF0YXNldCkKICB0YXJnZXQkVGFyZ2V0IDwtIGdzdWIoc2FtcGxlX25hbWUsICJQYXRpZW50IiwgdGFyZ2V0JFRhcmdldCkKICAKICBpZiAoIHBhcmFtcyRiYXRjaF9ybSApIHsKICAgIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInBjYV9jb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXSA8LSBwY2EoZGF0YSA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dLCB0YXJnZXRzID0gdGFyZ2V0LCB0aXRsZSA9ICJCZWZvcmUgYmF0Y2gtZWZmZWN0cyBjb3JyZWN0aW9uIiwgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyLCBzdWZmaXggPSAiX2JlZm9yZV9iYXRjaF9ybSIpCiAgICAKICAgIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInBjYV9iYXRjaF9lZmZlY3RfY29ycmVjdGVkIl1dIDwtIHBjYShkYXRhID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siYmF0Y2hfZWZmZWN0X2NvcnJlY3RlZCJdXSwgdGFyZ2V0cyA9IHRhcmdldCwgdGl0bGUgPSAiQWZ0ZXIgYmF0Y2gtZWZmZWN0cyBjb3JyZWN0aW9uIiwgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyLCBzdWZmaXggPSAiX2FmdGVyX2JhdGNoX3JtIikKICAgIAogICAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0gPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siYmF0Y2hfZWZmZWN0X2NvcnJlY3RlZCJdXQogICAgCiAgfSBlbHNlIHsKICAgIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInBjYV9jb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXSA8LSBwY2EoZGF0YSA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dLCB0YXJnZXRzID0gdGFyZ2V0LCByZXBvcnRfZGlyID0gcmVzdWx0c19kaXIpCiAgICAKICAgIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dCiAgfQp9CiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKYGBge3IgcmxlLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNiwgZmlnLnNob3c9ImhpZGUifQojIyMjIyBHZW5lcmF0ZSByZWxhdGl2ZSBsb2cgZXhwcmVzc2lvbiAoUkxFKSBwbG90IHVzaW5nIGNvbWJpbmVkLW9ubHkgZGF0YSBhbmQgYmF0Y2gtZWZmZWN0IGNvcnJlY3RlZCBkYXRhCiMjIyMjIExvb3AgdGhyb3VnaCBjb21iaW5lZCBkYXRhc2V0cyBhbmQgZ2VuZXJhdGUgUkxFIHBsb3QKZm9yICggZGF0YXNldCBpbiBuYW1lcyhyZWZfZGF0YXNldC5saXN0KSApIHsKICB0YXJnZXQgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sic2FtcGxlX2Fubm90Il1dCiAgdGFyZ2V0JERhdGFzZXQgPC0gZ3N1YihzYW1wbGVfbmFtZSwgIlBhdGllbnQiLCB0YXJnZXQkRGF0YXNldCkKICB0YXJnZXQkVGFyZ2V0IDwtIGdzdWIoc2FtcGxlX25hbWUsICJQYXRpZW50IiwgdGFyZ2V0JFRhcmdldCkKICAKICBpZiAoIHBhcmFtcyRiYXRjaF9ybSApIHsKICAgIHBhcihtZnJvdz1jKDIsMSksIG1hcj1jKDIsIDUsIDMsIDIpKQogICAgCiAgICAjIyMjIyBCZWZvcmUgYmF0Y2gtZWZmZWN0cyBjb3JyZWN0aW9uCiAgICBwbG90UkxFKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dLCBjb2w9dGFyZ2V0cy5jb2xvdXJbWzJdXSwgbWFpbj0iIiwgcGNoPSIiLCBsYXM9MywgeGF4dD0ibiIsIG91dGxpbmUgPSBGQUxTRSkKICAgIHRpdGxlKG1haW49IkJlZm9yZSBiYXRjaC1lZmZlY3RzIGNvcnJlY3Rpb24iLCB5bGFiPSJSTEUiKQogICAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZXZlbHMoZmFjdG9yKHRhcmdldCRUYXJnZXQpKSwgZmlsbD10YXJnZXRzLmNvbG91cltbMV1dLCBob3Jpej1UUlVFLCBiZyA9ICJ0cmFuc3BhcmVudCIsIGJveC5jb2w9InRyYW5zcGFyZW50IikKICAgIAogICAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicmxlX2NvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dIDwtIHJlY29yZFBsb3QoKQogICAgCiAgICAjIyMjIyBBZnRlciBiYXRjaC1lZmZlY3RzIGNvcnJlY3Rpb24KICAgIHBsb3RSTEUocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siYmF0Y2hfZWZmZWN0X2NvcnJlY3RlZCJdXSwgY29sPXRhcmdldHMuY29sb3VyW1syXV0sIG1haW49IiIsIHBjaD0iIiwgbGFzPTMsIHhheHQ9Im4iLCBvdXRsaW5lID0gRkFMU0UpCiAgICB0aXRsZShtYWluPSJBZnRlciBiYXRjaC1lZmZlY3RzIGNvcnJlY3Rpb24iLCB5bGFiPSJSTEUiKQogICAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZXZlbHMoZmFjdG9yKHRhcmdldCRUYXJnZXQpKSwgZmlsbD10YXJnZXRzLmNvbG91cltbMV1dLCBob3Jpej1UUlVFLCBiZyA9ICJ0cmFuc3BhcmVudCIsIGJveC5jb2w9InRyYW5zcGFyZW50IikKICAgIAogICAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicmxlX2JhdGNoX2VmZmVjdF9jb3JyZWN0ZWQiXV0gPC0gcmVjb3JkUGxvdCgpCiAgICAKICAgIAogICAgIyMjIyMgU2F2ZSB0aGUgcGxvdCBhcyBwbmcgZmlsZQogICAgcG5nKHBhc3RlMChQbG90c0RpciwgIi9ybGUucG5nIiksIHdpZHRoPTkwMCwgaGVpZ2h0PTcwMCwgcG9pbnRzaXplID0gMTQpCiAgICBwYXIobWZyb3c9YygyLDEpLCBtYXI9YygyLCA1LCAzLCAyKSkKICAKICAgICMjIyMjIEJlZm9yZSBiYXRjaC1lZmZlY3RzIGNvcnJlY3Rpb24KICAgIHBsb3RSTEUocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YV9wcm9jZXNzZWQiXV0sIGNvbD10YXJnZXRzLmNvbG91cltbMl1dLCBtYWluPSIiLCBwY2g9IiIsIGxhcz0zLCB4YXh0PSJuIiwgb3V0bGluZSA9IEZBTFNFKQogICAgdGl0bGUobWFpbj0iQmVmb3JlIGJhdGNoLWVmZmVjdHMgY29ycmVjdGlvbiIsIHlsYWI9IlJMRSIpCiAgICBsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxldmVscyhmYWN0b3IodGFyZ2V0JFRhcmdldCkpLCBmaWxsPXRhcmdldHMuY29sb3VyW1sxXV0sIGhvcml6PVRSVUUsIGJnID0gInRyYW5zcGFyZW50IiwgYm94LmNvbD0idHJhbnNwYXJlbnQiKQogIAogICAgIyMjIyMgQWZ0ZXIgYmF0Y2gtZWZmZWN0cyBjb3JyZWN0aW9uCiAgICBwbG90UkxFKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImJhdGNoX2VmZmVjdF9jb3JyZWN0ZWQiXV0sIGNvbD10YXJnZXRzLmNvbG91cltbMl1dLCBtYWluPSIiLCBwY2g9IiIsIGxhcz0zLCB4YXh0PSJuIiwgb3V0bGluZSA9IEZBTFNFKQogICAgdGl0bGUobWFpbj0iQWZ0ZXIgYmF0Y2gtZWZmZWN0cyBjb3JyZWN0aW9uIiwgeWxhYj0iUkxFIikKICAgIGxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9bGV2ZWxzKGZhY3Rvcih0YXJnZXQkVGFyZ2V0KSksIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgaG9yaXo9VFJVRSwgYmcgPSAidHJhbnNwYXJlbnQiLCBib3guY29sPSJ0cmFuc3BhcmVudCIpCiAgICBpbnZpc2libGUoZGV2Lm9mZigpKQoKICB9IGVsc2UgewogICAgcGxvdFJMRShyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXSwgY29sPXRhcmdldHMuY29sb3VyW1syXV0sIG1haW49IiIsIHBjaD0iIiwgbGFzPTMsIHhheHQ9Im4iLCBvdXRsaW5lID0gRkFMU0UpCiAgICB0aXRsZShtYWluPSIiLCB5bGFiPSJSTEUiKQogICAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1sZXZlbHMoZmFjdG9yKHRhcmdldCRUYXJnZXQpKSwgZmlsbD10YXJnZXRzLmNvbG91cltbMV1dLCBob3Jpej1UUlVFLCBiZyA9ICJ0cmFuc3BhcmVudCIsIGJveC5jb2w9InRyYW5zcGFyZW50IikKICAgIAogICAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicmxlX2NvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkIl1dIDwtIHJlY29yZFBsb3QoKQogICAgCiAgICAjIyMjIyBTYXZlIHRoZSBwbG90IGFzIHBuZyBmaWxlCiAgICBwbmcocGFzdGUwKFBsb3RzRGlyLCAiL3JsZS5wbmciKSwgd2lkdGg9OTAwLCBoZWlnaHQ9NDUwLCBwb2ludHNpemUgPSAxNCkKICAKICAgIHBsb3RSTEUocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YV9wcm9jZXNzZWQiXV0sIGNvbD10YXJnZXRzLmNvbG91cltbMl1dLCBtYWluPSIiLCBwY2g9IiIsIGxhcz0zLCB4YXh0PSJuIiwgb3V0bGluZSA9IEZBTFNFKQogICAgdGl0bGUobWFpbj0iIiwgeWxhYj0iUkxFIikKICAgIGxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9bGV2ZWxzKGZhY3Rvcih0YXJnZXQkVGFyZ2V0KSksIGZpbGw9dGFyZ2V0cy5jb2xvdXJbWzFdXSwgaG9yaXo9VFJVRSwgYmcgPSAidHJhbnNwYXJlbnQiLCBib3guY29sPSJ0cmFuc3BhcmVudCIpCiAgICBpbnZpc2libGUoZGV2Lm9mZigpKQogIH0KfQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKHRhcmdldHMuY29sb3VyLCBkZW4sIHkpCmBgYAoKYGBge3IgZ2VuZV9hbm5vdF9jb3VudF9kYXRhLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIExvb3AgdGhyb3VnaCBjb21iaW5lZCwgQlVUIE5PVCBQUk9DRVNTRUQsIGRhdGFzZXRzIGFuZCBhbm5vdGF0ZSBBTEwgZ2VuZXMuIFRoaXMgcGFydCBpcyBtYWlubHkgcmVxdWlyZWQgZm9yIGJpb3R5cGUgZGV0ZWN0aW9uIHN0ZXAKZm9yICggZGF0YXNldCBpbiBuYW1lcyhyZWZfZGF0YXNldC5saXN0KSApIHsKICAKICAjIyMjIyBDb252ZXJ0IGRhdGEgaW50byBhIGRhdGEgZnJhbWUgdG8gbWFrZSB0aGUgRW5zZW1ibCBJRCBhbmQgZ2VuZSBzeW1ib2wgbWF0Y2hlcyAod2l0aCBtZXJnZSBmdW5jdGlvbikKICBkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImNvbWJpbmVkX2RhdGEiXV0KICBkYXRhLmRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQocm93bmFtZXMoZGF0YSksIGRhdGEpKQogIGNvbG5hbWVzKGRhdGEuZGYpWzFdIDwtICJFTlNFTUJMIgoKICAjIyMjIyBHZXQgZ2VuZXMgYW5ub3RhdGlvbiBhbmQgZ2Vub21pYyBsb2NhdGlvbnMKICBlZGIgPC0gZXZhbChwYXJzZSh0ZXh0ID0gcGFzdGUwKCJFbnNEYi5Ic2FwaWVucy52IiwgcGFyYW1zJGVuc2VtYmxfdmVyc2lvbikpKQogIAogICMjIyMjIEdldCBrZXl0eXBlcyBmb3IgZ2VuZSBTWU1CT0wKICBrZXlzIDwtIGtleXMoZWRiLCBrZXl0eXBlPSJHRU5FSUQiKQogIAogICMjIyMjIEdldCBnZW5lcyBnZW5vbWljIGNvb3JkaWFudGVzCiAgZ2VuZV9pbmZvIDwtIGVuc2VtYmxkYjo6c2VsZWN0KGVkYiwga2V5cz1rZXlzLCBjb2x1bW5zPWMoIkdFTkVJRCIsICJHRU5FQklPVFlQRSIsICJHRU5FTkFNRSIsICJTRVFOQU1FIiwgIkdFTkVTRVFTVEFSVCIsICJHRU5FU0VRRU5EIiksIGtleXR5cGU9IkdFTkVJRCIpCiAgbmFtZXMoZ2VuZV9pbmZvKSA8LSBnc3ViKCJHRU5FSUQiLCAiRU5TRU1CTCIsIG5hbWVzKGdlbmVfaW5mbykpCiAgbmFtZXMoZ2VuZV9pbmZvKSA8LSBnc3ViKCJHRU5FTkFNRSIsICJTWU1CT0wiLCBuYW1lcyhnZW5lX2luZm8pKQogIAogICMjIyMjIExpbWl0IGdlbmVzIGFubm90YXRpb24gdG8gdGhvc2UgZ2VuZXMgZm9yIHdoaWNoIHNhbXBsZSBleHByZXNzaW9uIG1lYXN1cm1lbnRzIGFyZSBhdmFpbGFibGUKICBnZW5lX2luZm8gPC0gIGdlbmVfaW5mb1sgZ2VuZV9pbmZvJEVOU0VNQkwgJWluJSBkYXRhLmRmJEVOU0VNQkwsICBdCiAgCiAgIyMjIyMgUmVtb3ZlIHJvd3Mgd2l0aCBkdXBsaWNhdGVkIEVOU0VNQkwgSURzCiAgZ2VuZV9pbmZvID0gZ2VuZV9pbmZvWyFkdXBsaWNhdGVkKGdlbmVfaW5mbyRFTlNFTUJMKSxdCiAgcm93bmFtZXMoZ2VuZV9pbmZvKSA8LSBnZW5lX2luZm8kRU5TRU1CTAogIAogICMjIyMjIFJlbW92ZSByb3dzIHdpdGggZHVwbGljYXRlZCBnZW5lIHN5bWJvbHMgKFlfUk5BcywgU05PUnMsIExJTkMwcyBldGMpCiAgZ2VuZV9pbmZvID0gZ2VuZV9pbmZvWyFkdXBsaWNhdGVkKGdlbmVfaW5mbyRTWU1CT0wpLF0KICAKICAjIyMjIyBBZGQgaW5mbyBhYm91dCBpbW11bmUgcmVzcG9uc2UgbWFya2VycwogIGdlbmVfaW5mby5pbW11bmVfbWFya2VycyA8LSBtZXJnZShnZW5lX2luZm8sIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzLCBieSA9ICJTWU1CT0wiLCBhbGwueCA9IFRSVUUpCiAgCiAgIyMjIyMgS2VlcCBvbmx5IGltbXVuZSByZXNwb25zZSBtYXJrZXJzIGZvciB3aGljaCB0aGVyZSBpcyBhdmFpbGFibGUgYW5ub3RhdGlvbgogIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzIDwtIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzWyByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bmVfbWFya2VycyRTWU1CT0wgJWluJSBnZW5lX2luZm8uaW1tdW5lX21hcmtlcnMkU1lNQk9MLCBdCiAgCiAgIyMjIyMgQWRkIGluZm8gYWJvdXQgaW1tdW5vZ3JhbSBnZW5lcwogIGlmICggcGFyYW1zJGltbXVub2dyYW0gKSB7CiAgICBnZW5lX2luZm8uaW1tdW5vZ3JhbSA8LSBtZXJnZShnZW5lX2luZm8sIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0sIGJ5ID0gIlNZTUJPTCIsIGFsbC54ID0gVFJVRSkKICAgIGdlbmVfaW5mby5pbW11bm9ncmFtIDwtIGdlbmVfaW5mby5pbW11bm9ncmFtWyFkdXBsaWNhdGVkKGdlbmVfaW5mby5pbW11bm9ncmFtWywiRU5TRU1CTCJdKSxdCiAgICAKICAgICMjIyMjIEtlZXAgb25seSBpbW11bm9ncmFtIGdlbmVzIGZvciB3aGljaCB0aGVyZSBpcyBhdmFpbGFibGUgYW5ub3RhdGlvbgogICAgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbSA8LSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bm9ncmFtWyByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bm9ncmFtJFNZTUJPTCAlaW4lIGdlbmVfaW5mby5pbW11bm9ncmFtJFNZTUJPTCwgXQogICAgCiAgICAjIyMjIyBNZXJnZSBnZW5lcyBhbm5vdGF0aW9ucyBmb3IgaW1tdW5vZ3JhbSBnZW5lcyBhbmQgaW1tdW5lIG1hcmtlcnMKICAgIGdlbmVfaW5mbyA8LSBtZXJnZSggZ2VuZV9pbmZvLmltbXVub2dyYW0sIGdlbmVfaW5mby5pbW11bmVfbWFya2Vyc1sgLCBjKCJFTlNFTUJMIiwgIkltbXVuZV9DeWNsZV9Sb2xlIikgXSwgYnkgPSAiRU5TRU1CTCIpCiAgfSBlbHNlIHsKICAgIGdlbmVfaW5mbyA8LSBnZW5lX2luZm8uaW1tdW5lX21hcmtlcnMKICB9CiAgCiAgIyMjIyMgTWVyZ2UgZ2VuZXMgZ2Vub21pYyBjb29yZGluYXRlcyBpbmZvIHdpdGggdGhlaXIgYW5ub3RhdGlvbiBhbmQgZXhwcmVzc2lvbiBkYXRhCiAgZGF0YS5hbm5vdCA8LSBtZXJnZShnZW5lX2luZm8sIGRhdGEuZGYsIGJ5ID0gIkVOU0VNQkwiLCBhbGwueCA9IEZBTFNFKQogIHJvd25hbWVzKGRhdGEuYW5ub3QpIDwtIGRhdGEuYW5ub3QkRU5TRU1CTAogIAogICMjIyMjIEdldCBkYXRhIG1hdHJpeCB3aXRoIGdlbmUgc3ltYm9scwogIGlmICggcGFyYW1zJGltbXVub2dyYW0gKSB7CiAgICByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSA8LSBkYXRhLmFubm90WywgYygiU1lNQk9MIiwgIkdFTkVCSU9UWVBFIiwgIkVOU0VNQkwiLCAiU0VRTkFNRSIsICJHRU5FU0VRU1RBUlQiLCAiR0VORVNFUUVORCIsICJDSUMiLCAiSW1tdW5lX0N5Y2xlX1JvbGUiKV0KICB9IGVsc2UgewogICAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV0gPC0gZGF0YS5hbm5vdFssIGMoIlNZTUJPTCIsICJHRU5FQklPVFlQRSIsICJFTlNFTUJMIiwgIlNFUU5BTUUiLCAiR0VORVNFUVNUQVJUIiwgIkdFTkVTRVFFTkQiLCAiSW1tdW5lX0N5Y2xlX1JvbGUiKV0KICB9CiAgCiAgIyMjIyMgU2F2ZSB0aGUgY29tYmluZWQgZXhwcmVzc2lvbiBtYXRyaXgsIGdlbmVzIGxpc3QgYW5kIGFzc29jaWF0ZWQgdGFyZ2V0cyBpbnRvIHR4dCBmaWxlcwogIHdyaXRlLnRhYmxlKHByZXBhcmUyd3JpdGUocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSksIGZpbGUgPSBwYXN0ZTAocmVzdWx0c19kaXIsICIvIiwgc2FtcGxlX25hbWUsICIuUk5Bc2VxX3JlcG9ydC5jb21iaW5lZF9kYXRhLnR4dCIpLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIHJvdy5uYW1lcz1GQUxTRSwgY29sLm5hbWVzPVRSVUUsIGFwcGVuZCA9IEZBTFNFICkKICB3cml0ZS50YWJsZShwcmVwYXJlMndyaXRlKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dKSwgZmlsZSA9IHBhc3RlMChyZXN1bHRzX2RpciwgIi8iLCBzYW1wbGVfbmFtZSwgIi5STkFzZXFfcmVwb3J0LmdlbmVfYW5ub3RfYWxsLnR4dCIpLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIHJvdy5uYW1lcz1GQUxTRSwgY29sLm5hbWVzPVRSVUUsIGFwcGVuZCA9IEZBTFNFICkKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGRhdGEsIHRhcmdldCwgZGF0YS5kZiwgZWRiLCBrZXlzKQpgYGAKCmBgYHtyIGdlbmVfYW5ub3RfcHJvY2Vzc2VkX2RhdGEsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyMjIyMgTG9vcCB0aHJvdWdoIGNvbWJpbmVkIGRhdGFzZXRzIGFuZCBhbm5vdGF0ZSBnZW5lcwpmb3IgKCBkYXRhc2V0IGluIG5hbWVzKHJlZl9kYXRhc2V0Lmxpc3QpICkgewogIAogICMjIyMjIENvbnZlcnQgZGF0YSBpbnRvIGEgZGF0YSBmcmFtZSB0byBtYWtlIHRoZSBFbnNlbWJsIElEIGFuZCBnZW5lIHN5bWJvbCBtYXRjaGVzICh3aXRoIG1lcmdlIGZ1bmN0aW9uKQogIGRhdGEgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0KICBkYXRhLmRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQocm93bmFtZXMoZGF0YSksIGRhdGEpKQogIGNvbG5hbWVzKGRhdGEuZGYpWzFdIDwtICJFTlNFTUJMIgogIAogICMjIyMjIE1lcmdlIGdlbmVzIGdlbm9taWMgY29vcmRpbmF0ZXMgaW5mbyB3aXRoIHRoZWlyIGFubm90YXRpb24gYW5kIGV4cHJlc3Npb24gZGF0YQogIGRhdGEuYW5ub3QgPC0gbWVyZ2UoZ2VuZV9pbmZvLCBkYXRhLmRmLCBieSA9ICJFTlNFTUJMIiwgYWxsLnggPSBGQUxTRSkKICAKICAjIyMjIyBLZWVwIG9ubHkgZ2VuZXMgZm8gd2hpY2ggZ2VuZSBzeW1ib2wgaXMgYXZhaWxhYmxlCiAgZGF0YS5hbm5vdCA8LSBkYXRhLmFubm90WyEoaXMubmEoZGF0YS5hbm5vdCRTWU1CT0wpIHwgZGF0YS5hbm5vdCRTWU1CT0w9PSIiKSwgXQogIHJvd25hbWVzKGRhdGEuYW5ub3QpIDwtIGRhdGEuYW5ub3QkU1lNQk9MCiAgCiAgIyMjIyMgR2V0IGRhdGEgbWF0cml4IHdpdGggZ2VuZSBzeW1ib2xzCiAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0gPC0gYXBwbHkoZGF0YS5hbm5vdFssIGNvbG5hbWVzKGRhdGEpXSwgMiwgYXMubnVtZXJpYykKICByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkgPC0gZGF0YS5hbm5vdCRTWU1CT0wKICByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90Il1dIDwtIGRhdGEuYW5ub3RbLCBjKCJTWU1CT0wiLCAiR0VORUJJT1RZUEUiLCAiRU5TRU1CTCIsICJTRVFOQU1FIiwgIkdFTkVTRVFTVEFSVCIsICJHRU5FU0VRRU5EIiwgIkltbXVuZV9DeWNsZV9Sb2xlIildCiAgCiAgIyMjIyMgU2F2ZSB0aGUgY29tYmluZWQgZXhwcmVzc2lvbiBtYXRyaXgsIGdlbmVzIGxpc3QgYW5kIGFzc29jaWF0ZWQgdGFyZ2V0cyBpbnRvIHR4dCBmaWxlcwogIHdyaXRlLnRhYmxlKHByZXBhcmUyd3JpdGUocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pLCBmaWxlID0gcGFzdGUwKHJlc3VsdHNfZGlyLCAiLyIsIHNhbXBsZV9uYW1lLCAiLlJOQXNlcV9yZXBvcnQuY29tYmluZWRfZGF0YV9wcm9jZXNzZWQudHh0IiksIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgcm93Lm5hbWVzPUZBTFNFLCBjb2wubmFtZXM9VFJVRSwgYXBwZW5kID0gRkFMU0UgKQogIHdyaXRlLnRhYmxlKHByZXBhcmUyd3JpdGUodG91cHBlcihyb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKSwgZmlsZSA9IHBhc3RlMChyZXN1bHRzX2RpciwgIi8iLCBzYW1wbGVfbmFtZSwgIi5STkFzZXFfcmVwb3J0LmNvbWJpbmVkX2RhdGFfcHJvY2Vzc2VkLmdlbmVzLnR4dCIpLCBzZXA9Ilx0IiwgcXVvdGU9RkFMU0UsIHJvdy5uYW1lcz1GQUxTRSwgY29sLm5hbWVzPVRSVUUsIGFwcGVuZCA9IEZBTFNFICkKICB3cml0ZS50YWJsZShwcmVwYXJlMndyaXRlKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXSksIGZpbGUgPSBwYXN0ZTAocmVzdWx0c19kaXIsICIvIiwgc2FtcGxlX25hbWUsICIuUk5Bc2VxX3JlcG9ydC5zYW1wbGVfYW5ub3QudHh0IiksIHNlcD0iXHQiLCBxdW90ZT1GQUxTRSwgcm93Lm5hbWVzPUZBTFNFLCBjb2wubmFtZXM9VFJVRSwgYXBwZW5kID0gRkFMU0UgKQp9CgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0oZGF0YSwgZGF0YS5kZiwgZ2VuZV9pbmZvKQpgYGAKCmBgYHtyIGdlbmVfYW5ub3RfcHJvY2Vzc2VkX2RhdGFfc2F2ZSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPXBhcmFtcyRzYXZlX3RhYmxlc30KIyMjIyMgU2F2ZSB0aGUgZW50aXJlIGV4cHJlc3Npb24gZGF0YSBmb3IgYWxsIGdlbmVzIG1lYXN1cmVkIGluIHBhdGllbnQncyBzYW1wbGUgd2l0aCBjYW5jZXIgZ2VuZXMgYW5ub3RhaXRvbiBhcyBhIGRhdGEgdGFibGUgaHRtbCBmaWxlCiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgbXV0YXRlZCBnZW5lcwp0YXJnZXRzIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dCgojIyMjIyBQZXJjZW50aWxlcwpnZW5lcy5leHByLnBlcmMgPC0gZXhwclRhYmxlKCBnZW5lcyA9IHJvd25hbWVzKGRhdGEpLCBrZWVwX2FsbCA9IFRSVUUsIGRhdGEgPSBkYXRhLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSwgZXh0X2xpbmtzID0gVFJVRSwgdHlwZSA9ICJwZXJjIiwgc2NhbGluZyA9IHNjYWxpbmcpCgojIyMjIyBaLXNjb3JlcwpnZW5lcy5leHByLnogPC0gZXhwclRhYmxlKCBnZW5lcyA9IHJvd25hbWVzKGRhdGEpLCBrZWVwX2FsbCA9IFRSVUUsIGRhdGEgPSBkYXRhLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSwgZXh0X2xpbmtzID0gVFJVRSwgdHlwZSA9ICJ6Iiwgc2NhbGluZyA9IHNjYWxpbmcpCgojIyMjIyBDcmVhdGUgZGlyZWN0b3J5IGZvciBzYXZpbmcgdGFibGVzCmV4cHJUYWJsZURpciA8LSBwYXN0ZShyZXN1bHRzX2RpciwgImV4cHJUYWJsZXMiLCBzZXAgPSAiLyIpCiAgICAKaWYgKCAhZmlsZS5leGlzdHMoZXhwclRhYmxlRGlyKSApIHsKICBkaXIuY3JlYXRlKGV4cHJUYWJsZURpciwgcmVjdXJzaXZlPVRSVUUpCn0KCiMjIyMjIFNhdmUgdGhlIGV4cHJlc3Npb24gdGFibGVzIGFzIGh0bWwgZmlsZQpzYXZlV2lkZ2V0Rml4KHdpZGdldD1nZW5lcy5leHByLnBlcmNbWzFdXSwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJnZW5lcy5leHByLnBlcmMuaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKc2F2ZVdpZGdldEZpeCh3aWRnZXQ9Z2VuZXMuZXhwci56W1sxXV0sIGZpbGU9cGFzdGUoZXhwclRhYmxlRGlyLCAiZ2VuZXMuZXhwci56Lmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0oZGF0YSwgdGFyZ2V0cywgZ2VuZXMuZXhwci56LCBnZW5lcy5leHByLnBlcmMpCmBgYAoKYGBge3IgY25fZXhwcl9kYXRhX3ByZXAsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blB1cnBsZUNodW5rfQojIyMjIyBDb21iaW5lIGV4cHJlc3Npb24gZGF0YSB3aXRoIG11dGF0aW9uIGFuZCBDTiBkYXRhIGlmIGF2YWlsYWJsZQpjbl9kYXRhIDwtIHJlZl9nZW5lcy5saXN0W1sicHVycGxlIl1dCmV4cHJfZGF0YSA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXQp0YXJnZXRzIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQoKIyMjIyMgLi4ucGVyY2VybnRpbGVzCmV4cHJfZGF0YS5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSByb3duYW1lcyhleHByX2RhdGEpLCBrZWVwX2FsbCA9IFRSVUUsIGRhdGEgPSBleHByX2RhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIHR5cGUgPSAicGVyYyIsIHNjYWxpbmcgPSBzY2FsaW5nKVtbMl1dCgpleHByX2dlbmVzIDwtIGV4cHJfZGF0YS5wZXJjJFNZTUJPTAoKIyMjIyMgR2V0IHRoZSAiRGlmZiIgKFBhdGllbnQgdnMgW2NvbXBfY2FuY2VyXSkgWi1zY29yZXMgdXNpbmcgZXhwclRhYmxlIGZ1bmN0aW9uCmV4cHJfZGF0YS56IDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBleHByX2dlbmVzLCBrZWVwX2FsbCA9IFRSVUUsIGRhdGEgPSBleHByX2RhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIHR5cGUgPSAieiIsIHNjYWxpbmcgPSBzY2FsaW5nKVtbMl1dCgojIyMjIyBNYWtlIHN1cmUgdGhlIHRhYmxlcyBoYXZlIHRoZSBzYW1lIGdlbmVzIG9yZGVyCmV4cHJfZGF0YS5wZXJjIDwtIGV4cHJfZGF0YS5wZXJjWyBleHByX2dlbmVzLCBdCgppZiAoIGNvbXBfY2FuY2VyX2dyb3VwICE9IGludF9jYW5jZXJfZ3JvdXAgKSB7CiAgZXhwcl9kYXRhLnBlcmMgPC0gZXhwcl9kYXRhLnBlcmNbLCAiRGlmZiIgXQogIGV4cHJfZGF0YS56IDwtIGV4cHJfZGF0YS56WywgIkRpZmYiIF0KfSBlbHNlIHsKICBleHByX2RhdGEucGVyYyA8LSBleHByX2RhdGEucGVyY1ssIHBhc3RlMCggIlBhdGllbnQgdnMgIiwgY29tcF9jYW5jZXJfZ3JvdXApXQogIGV4cHJfZGF0YS56IDwtIGV4cHJfZGF0YS56WywgcGFzdGUwKCAiUGF0aWVudCB2cyAiLCBjb21wX2NhbmNlcl9ncm91cCldCn0KCm5hbWVzKGV4cHJfZGF0YS5wZXJjKSA8LSBleHByX2dlbmVzCm5hbWVzKGV4cHJfZGF0YS56KSA8LSBleHByX2dlbmVzCgojIyMjIyBDYWxjdWxhdGUgdGhlIG1lYW4gQ04gZm9yIGVhY2ggZ2VuZQpjbl9kYXRhJE1lYW5Db3B5TnVtYmVyIDwtIHJvd01lYW5zKGNiaW5kKGNuX2RhdGEkTWluQ29weU51bWJlciwgY25fZGF0YSRNYXhDb3B5TnVtYmVyKSkKICAKIyMjIyMgRGVhbCB3aXRoIG5lZ2F0aXZlIENOIHZhbHVlcwpjbl9kYXRhJE1lYW5Db3B5TnVtYmVyWyBjbl9kYXRhJE1lYW5Db3B5TnVtYmVyIDwgMCBdIDwtIDAKCiMjIyMjIFJlbW92ZSBlbnRyaWVzIHdpdGggbWlzc2luZyBnZW5lIHN5bWJvbCAobWFpbmx5IHZhcmlhbnRzIGluIGludGVyZ2VuaWMgcmVnaW9ucykKY25fZGF0YSA8LSBjbl9kYXRhWyBjbl9kYXRhJEdlbmUgJSFpbiUgIiIsIF0KCiMjIyMjIEtlZXAgb25seSBhbHRlcmVkIGdlbmVzIHdpdGggQ04gdmFsdWVzIGJlbG93IGxvc3MgdGhyZXNob2xkIChkZWZhdWx0IDV0aCBwZXJjZW50aWxlKSBhbmQgYWJvdmUgZ2FpbiB0aHJlc2hvbGQgKGRlZmF1bHQgOTV0aCBwZXJjZW50aWxlKQpjbl9kYXRhLmFsbCA8LSBjbl9kYXRhCgojIyMjIyBHZXQgdGhlIHBlcmNlbnRpbGVzIGZyb20gZnJvbSB0aGUgQ04gdmFsdWVzCmNuX2RhdGEuYWxsLnBlcmNlbnQgPC0gcXVhbnRpbGUoY25fZGF0YS5hbGwkTWVhbkNvcHlOdW1iZXIsIHByb2JzID0gc2VxKDAsIDEsIC4wNSksIG5hLnJtID0gVFJVRSkKCiMjIyMjIEtlZXAgb25seSBnZW5lcyB3aXRoIGF2YWlsYWJsZSBleHByZXNzaW9uIGRhdGEKY25fZGF0YSA8LSBjbl9kYXRhWyBjbl9kYXRhJEdlbmUgJWluJSBuYW1lcyhleHByX2RhdGEueiksIF0KCiMjIyMjIEFkZCBtdXRhdGlvbiBkYXRhIGlmIGF2YWlsYWJsZQppZiAoICFpcy5udWxsKHJlZl9nZW5lcy5saXN0W1sicGNnciJdXSkgKSB7CiAgbXV0X2RhdGEgPC0gcmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dCiAgCiAgIyMjIyMgUmVtb3ZlIGVudHJpZXMgd2l0aCBtaXNzaW5nIGdlbmUgc3ltYm9sIChtYWlubHkgdmFyaWFudHMgaW4gaW50ZXJnZW5pYyByZWdpb25zKQogIG11dF9kYXRhIDwtIG11dF9kYXRhWyBtdXRfZGF0YSRTWU1CT0wgJSFpbiUgIiIsIF0KCiAgIyMjIyMgUHJlcGFyZSBtdXRhdGlvbiBkYXRhIHRvIGluY2x1ZGUgbXVsdGlwbGUgbXV0YXRpb25zIHBlciBnZW5lCiAgIyMjIyMgSW5pdGlhdGUgdmFyaWFibGUgZm9yIHRoZSBnZW5lIG11dGF0aW9uIHN0YXR1cyBmb3IgZWFjaCBnZW5lCiAgZ2VuZS5tdXQgPC0gYXMubWF0cml4KHJlcCgiTm9uZSIsIGxlbmd0aChleHByX2RhdGEueikpKQogIGNvbG5hbWVzKGdlbmUubXV0KSA8LSAiQWx0ZXJhdGlvbnMiCiAgcm93bmFtZXMoZ2VuZS5tdXQpIDwtIG5hbWVzKGV4cHJfZGF0YS56KQoKICBmb3IgKCBpIGluIDE6bnJvdyhnZW5lLm11dCkgKSB7CiAgICAjIyMjIyBDaGVjayBpZiBhbnkgbXV0YXRpb25zIGFyZSByZXBvcnRlZCBmb3IgZWFjaCBnZW5lCiAgICBpZiAoICByb3duYW1lcyhnZW5lLm11dClbaV0gJWluJSBtdXRfZGF0YSRTWU1CT0wgKSB7CiAgICAKICAgICAgIyMjIyMgRGVhbCB3aXRoIG11bHRpcGxlIG11dGF0aW9ucyBwZXIgZ2VuZQogICAgICBpZiAoIGxlbmd0aChtdXRfZGF0YVsgbXV0X2RhdGEkU1lNQk9MICVpbiUgcm93bmFtZXMoZ2VuZS5tdXQpW2ldLCAgXSRDT05TRVFVRU5DRSkgPiAxICkgewogICAgICAgIGdlbmUubXV0WyByb3duYW1lcyhnZW5lLm11dClbaV0sIkFsdGVyYXRpb25zIiBdIDwtICJNdXRhdGlvbjogbXVsdGlwbGUgaGl0cyIKICAgICAgfSBlbHNlIHsKICAgICAgICBnZW5lLm11dFsgcm93bmFtZXMoZ2VuZS5tdXQpW2ldLCJBbHRlcmF0aW9ucyIgXSA8LSBwYXN0ZTAoIk11dGF0aW9uOiAiLCBtdXRfZGF0YVsgbXV0X2RhdGEkU1lNQk9MICVpbiUgcm93bmFtZXMoZ2VuZS5tdXQpW2ldLCAgXSRDT05TRVFVRU5DRSkKICAgICAgfQogICAgfQogIH0KCiAgIyMjIyMgSWYgdGhlcmUgaXMgbm8gZXhwcmVzc2lvbiB2YWx1ZSBmb3IgYSBzcGVjaWZpYyBnZW5lIHRoYW4gYXNzdW1lIGl0J3Mgbm90IGV4cHJlc3NlZCBhdCBhbGwgYW5kIGFzc2lnbiB0aGUgbG93ZXN0IHZhbHVlIG9ic2VydmVkIGluIHRoYXQgc2FtcGxlCiAgZm9yICggZ2VuZSBpbiB1bmlxdWUobXV0X2RhdGEkU1lNQk9MKSApIHsKICAgIGlmICggZ2VuZSAlIWluJSByb3duYW1lcyhnZW5lLm11dCkgKSB7CiAgICAgIAogICAgICBleHByX2RhdGEucGVyYyA8LSBjKGV4cHJfZGF0YS5wZXJjLCBtaW4oZXhwcl9kYXRhLnBlcmMpKQogICAgICBuYW1lcyhleHByX2RhdGEucGVyYylbbGVuZ3RoKGV4cHJfZGF0YS5wZXJjKV0gPC0gZ2VuZQogICAgICAKICAgICAgZXhwcl9kYXRhLnogPC0gYyhleHByX2RhdGEueiwgbWluKGV4cHJfZGF0YS56KSkKICAgICAgbmFtZXMoZXhwcl9kYXRhLnopW2xlbmd0aChleHByX2RhdGEueildIDwtIGdlbmUKICAgICAgCiAgICAgICMjIyMjIERlYWwgd2l0aCBtdWx0aXBsZSBtdXRhdGlvbnMgcGVyIGdlbmUKICAgICAgaWYgKCBsZW5ndGgobXV0X2RhdGFbIG11dF9kYXRhJFNZTUJPTCAlaW4lIGdlbmUsICBdJENPTlNFUVVFTkNFKSA+IDEgKSB7CiAgICAgICAgZ2VuZS5tdXQgPC0gcmJpbmQoIGdlbmUubXV0LCAgIm11bHRpcGxlIGhpdHMiKQogICAgICB9IGVsc2UgewogICAgICAgIGdlbmUubXV0IDwtIHJiaW5kKCBnZW5lLm11dCwgIG11dF9kYXRhWyBtdXRfZGF0YSRTWU1CT0wgJWluJSBnZW5lLCAgXSRDT05TRVFVRU5DRSApCiAgICAgIH0KICAgICAgcm93bmFtZXMoZ2VuZS5tdXQpW25yb3coZ2VuZS5tdXQpXSA8LSBnZW5lCiAgICB9CiAgfQoKICAjIyMjIyBTdWJzZXQgZXhwcmVzc2lvbiwgbXV0YXRpb24gYW5kIGNvcHktbnVtYmVyIGRhdGEgdG8gaW5jbHVkZSBvbmx5IG92ZXJsYXBwaW5nIGdlbmVzCiAgZ2VuZXMuaW50ZXJzZWN0IDwtIGludGVyc2VjdChpbnRlcnNlY3Qocm93bmFtZXMoZ2VuZS5tdXQpLCBjbl9kYXRhJEdlbmUpLCBuYW1lcyhleHByX2RhdGEucGVyYykpCiAgCiAgZ2VuZS5tdXQuc3ViIDwtIGdlbmUubXV0WyByb3duYW1lcyhnZW5lLm11dCkgJWluJSBnZW5lcy5pbnRlcnNlY3QsIF0KICBjbl9kYXRhLnN1YiA8LSBjbl9kYXRhWyBjbl9kYXRhJEdlbmUgJWluJSBnZW5lcy5pbnRlcnNlY3QsIF0KICBleHByX2RhdGEucGVyYy5zdWIgPC0gZXhwcl9kYXRhLnBlcmNbIG5hbWVzKGV4cHJfZGF0YS5wZXJjKSAlaW4lIGdlbmVzLmludGVyc2VjdCBdCiAgZXhwcl9kYXRhLnouc3ViIDwtIGV4cHJfZGF0YS56WyBuYW1lcyhleHByX2RhdGEueikgJWluJSBnZW5lcy5pbnRlcnNlY3QgXQogIAogICMjIyMjIE1ha2Ugc3VyZSB0aGF5IGFyZSBhbGwgaW4gdGhlIHNhbWUgb3JkZXIKICBnZW5lLm11dC5zdWIgPC0gZ2VuZS5tdXQuc3ViWyBnZW5lcy5pbnRlcnNlY3QgXQogIHJvd25hbWVzKGNuX2RhdGEuc3ViKSA8LSBjbl9kYXRhLnN1YiRHZW5lCiAgY25fZGF0YS5zdWIgPC0gY25fZGF0YS5zdWJbIGdlbmVzLmludGVyc2VjdCwgIF0KICBleHByX2RhdGEucGVyYy5zdWIgPC0gZXhwcl9kYXRhLnBlcmMuc3ViWyBnZW5lcy5pbnRlcnNlY3QgIF0KICBleHByX2RhdGEuei5zdWIgPC0gZXhwcl9kYXRhLnouc3ViWyBnZW5lcy5pbnRlcnNlY3QgIF0KICAKICAjIyMjIyBQcmVwYXJlIGRhdGEgZnJhbWUKICBjbl9kYXRhLnN1YiA8LSBkYXRhLmZyYW1lKG5hbWVzKGV4cHJfZGF0YS56LnN1YiksIGNuX2RhdGEuc3ViJE1lYW5Db3B5TnVtYmVyLCBleHByX2RhdGEucGVyYy5zdWIsIGV4cHJfZGF0YS56LnN1YiwgZ2VuZS5tdXQuc3ViKQogIGNvbG5hbWVzKGNuX2RhdGEuc3ViKSA8LSBjKCJHZW5lIiwgIkNOIiwgIlBlcmNfZGlmZiIsICJaX3Njb3JlX2RpZmYiLCAiQWx0ZXJhdGlvbnMiKQogIAp9IGVsc2UgewogICMjIyMjIFNraXAgdGhlIHN0ZXAgZm9yIHByb2Nlc3NpbmcgbXV0YXRpb24gaW5mbyBhbmQgZGVhbCB3aXRoIGV4cHJlc3Npb24gYW5kIGNvcHktbnVtYmVyIGRhdGEKICAjIyMjIyBTdWJzZXQgZXhwcmVzc2lvbiBhbmQgY29weS1udW1iZXIgZGF0YSB0byBpbmNsdWRlIG9ubHkgb3ZlcmxhcHBpbmcgZ2VuZXMKICBnZW5lcy5pbnRlcnNlY3QgPC0gaW50ZXJzZWN0KGNuX2RhdGEkR2VuZSwgbmFtZXMoZXhwcl9kYXRhLnBlcmMpKQogIAogIGNuX2RhdGEuc3ViIDwtIGNuX2RhdGFbIGNuX2RhdGEkR2VuZSAlaW4lIGdlbmVzLmludGVyc2VjdCwgXQogIGV4cHJfZGF0YS5wZXJjLnN1YiA8LSBleHByX2RhdGEucGVyY1sgbmFtZXMoZXhwcl9kYXRhLnBlcmMpICVpbiUgZ2VuZXMuaW50ZXJzZWN0IF0KICBleHByX2RhdGEuei5zdWIgPC0gZXhwcl9kYXRhLnpbIG5hbWVzKGV4cHJfZGF0YS56KSAlaW4lIGdlbmVzLmludGVyc2VjdCBdCiAgCiAgIyMjIyMgTWFrZSBzdXJlIHRoYXkgYXJlIGFsbCBpbiB0aGUgc2FtZSBvcmRlcgogIHJvd25hbWVzKGNuX2RhdGEuc3ViKSA8LSBjbl9kYXRhLnN1YiRHZW5lCiAgY25fZGF0YS5zdWIgPC0gY25fZGF0YS5zdWJbIGdlbmVzLmludGVyc2VjdCwgIF0KICBleHByX2RhdGEucGVyYy5zdWIgPC0gZXhwcl9kYXRhLnBlcmMuc3ViWyBnZW5lcy5pbnRlcnNlY3QgIF0KICBleHByX2RhdGEuei5zdWIgPC0gZXhwcl9kYXRhLnouc3ViWyBnZW5lcy5pbnRlcnNlY3QgIF0KICAKICAjIyMjIyBQcmVwYXJlIGRhdGEgZnJhbWUKICBjbl9kYXRhLnN1YiA8LSBkYXRhLmZyYW1lKG5hbWVzKGV4cHJfZGF0YS56LnN1YiksIGNuX2RhdGEuc3ViJE1lYW5Db3B5TnVtYmVyLCBleHByX2RhdGEucGVyYy5zdWIsIGV4cHJfZGF0YS56LnN1YikKICBjb2xuYW1lcyhjbl9kYXRhLnN1YikgPC0gYygiR2VuZSIsICJDTiIsICJQZXJjX2RpZmYiLCAiWl9zY29yZV9kaWZmIikKfQoKcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YV9hbGwiXV0gPC0gY25fZGF0YS5zdWIKCiMjIyMjIExpbWl0IHRoZSBkYXRhIHRvIGluY2x1ZGUgb25seSBjYW5jZXIgZ2VuZXMKY25fZGF0YS5zdWIgPC0gY25fZGF0YS5zdWJbIGNuX2RhdGEuc3ViJEdlbmUgJWluJSByb3duYW1lcyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSksIF0KCiMjIyMjIEtlZXAgZ2VuZXMgbWVldGluZyB0aGUgdXNlci1kZWZpbmVkIENOIHZhbHVlcyB0aHJlc2hvbGRzCnJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImV4cHJfbXV0X2NuX2RhdGEiXV0gPC0gY25fZGF0YS5zdWJbIGNuX2RhdGEuc3ViJENOIDw9IGNuX2JvdHRvbSB8IGNuX2RhdGEuc3ViJENOID49IGNuX3RvcCwgXQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGNuX2RhdGEsIGNuX2RhdGEuc3ViLCBleHByX2RhdGEsIGdlbmUubXV0LCBtdXRfZGF0YSwgdGFyZ2V0cywgZXhwcl9kYXRhLnosIGV4cHJfZGF0YS5wZXJjLCBleHByX2RhdGEuei5zdWIsIGV4cHJfZGF0YS5wZXJjLnN1YiwgZXhwcl9nZW5lcywgZ2VuZS5tdXQuc3ViLCBnZW5lcy5pbnRlcnNlY3QpCmBgYAoKYGBge3IgY25fZGF0YV9kaXN0cmlidXRpb24sIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blB1cnBsZUNodW5rfQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGxvdGx5KSkKCiMjIyMjIERyYXcgaGlzdG9ncmFtIG9mIENOIGRhdGEKY25fZGlzdF9wbG90IDwtIHBsb3RfbHkoeCA9IGNuX2RhdGEuYWxsJE1lYW5Db3B5TnVtYmVyLCB0eXBlID0gJ2hpc3RvZ3JhbScsIG5hbWUgPSAiQ04gZGF0YSIsIHdpZHRoID0gODAwLCBoZWlnaHQgPSAzMDApICU+JQogIAogICMjIyMjIEFkZCA1dGggcGVyY2VudGlsZSB0aHJlc2hvbGQKICBhZGRfbGluZXMoeSA9IHNlcSgwLDEwMDAsIDEwMCksIHggPSByZXAoY25fZGF0YS5hbGwucGVyY2VudFsyXSwxMSksIAogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsYWNrIiwgZGFzaCA9ICJkYXNoIiksIG9wYWNpdHkgPSAwLjQsCiAgICAgICAgICAgICAgbmFtZSA9ICI1dGggcGVyY2VudGlsZSIsIHNob3dsZWdlbmQgPSBUUlVFKSAlPiUKICAKICAjIyMjIyBBZGQgNTB0aCBwZXJjZW50aWxlCiAgYWRkX2xpbmVzKHkgPSBzZXEoMCwxMDAwLCAxMDApLCB4ID0gcmVwKGNuX2RhdGEuYWxsLnBlcmNlbnRbMTFdLDExKSwgCiAgICAgICAgICAgICAgbGluZSA9IGxpc3QoY29sb3IgPSAiYmxhY2siLCBkYXNoID0gImRhc2giKSwgb3BhY2l0eSA9IDAuNywKICAgICAgICAgICAgICBuYW1lID0gIjUwdGggcGVyY2VudGlsZSIsIHNob3dsZWdlbmQgPSBUUlVFKSAlPiUKICAKICAjIyMjIyBBZGQgOTV0aCBwZXJjZW50aWxlIHRocmVzaG9sZAogIGFkZF9saW5lcyh5ID0gc2VxKDAsMTAwMCwgMTAwKSwgeCA9IHJlcChjbl9kYXRhLmFsbC5wZXJjZW50WzIwXSwxMSksIAogICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gImJsYWNrIiwgZGFzaCA9ICJkYXNoIiksIG9wYWNpdHkgPSAxLAogICAgICAgICAgICAgIG5hbWUgPSAiOTV0aCBwZXJjZW50aWxlIiwgc2hvd2xlZ2VuZCA9IFRSVUUpICU+JQogIAogIGxheW91dCh4YXhpcyA9IGxpc3QoIHRpdGxlID0gIkNOIHZhbHVlcyIpLCB5YXhpcyA9IGxpc3QoIHRpdGxlID0gIkZyZXF1ZW5jeSIpLCBtYXJnaW4gPSBsaXN0KGw9NTAsIHI9NTAsIGI9NTAsIHQ9NTAsIHBhZD00KSwgYXV0b3NpemUgPSBGKQoKIyMjIyMgRGV0YWNoIHBsb3RseSBwYWNrYWdlLiBPdGhlcndpc2UgaXQgY2xhc2hlcyB3aXRoIG90aGVyIGdyYXBoaWNzIGRldmljZXMKZGV0YWNoKCJwYWNrYWdlOnBsb3RseSIsIHVubG9hZD1GQUxTRSkKCiMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQppZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShjbl9kYXRhLmFsbCkKYGBgCgpgYGB7ciBrbm93bl9mdXNpb25zX3ByZXAsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1bkZ1c2lvbkNodW5rfQojIyMjIyBGbGFnIGtub3duIGZ1c2lvbnMgYmFzZWQgb24gaW5mbyBmcm9tIENhbmNlciBCaW9tYXJrZXJzIGRhdGFiYXNlIChDR0kpIChodHRwczovL3d3dy5jYW5jZXJnZW5vbWVpbnRlcnByZXRlci5vcmcvYmlvbWFya2VycykKa25vd25fdHJhbnNsb2NhdGlvbnMuQ0dJIDwtIGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjYW5jZXJfYmlvbWFya2Vyc190cmFucyJdXQprbm93bl90cmFuc2xvY2F0aW9ucy5DR0kkY2FuY2VyX2Fjcm9ueW0gPC0gZ3N1YigiOyIsICIsICIsIGtub3duX3RyYW5zbG9jYXRpb25zLkNHSSRjYW5jZXJfYWNyb255bSkKa25vd25fdHJhbnNsb2NhdGlvbnMuQ0dJJHNvdXJjZSA8LSBnc3ViKCI7IiwgIiwgIiwga25vd25fdHJhbnNsb2NhdGlvbnMuQ0dJJHNvdXJjZSkKa25vd25fdHJhbnNsb2NhdGlvbnMuQ0dJJHRyYW5zbG9jYXRpb24gPC0gZ3N1YigiX18iLCAiXyIsIGtub3duX3RyYW5zbG9jYXRpb25zLkNHSSR0cmFuc2xvY2F0aW9uKQogIAojIyMjIyBGbGFnIGtub3duIGZ1c2lvbnMgYmFzZWQgb24gaW5mbyBmcm9tIEZ1c2lvbkdEQiAoaHR0cHM6Ly9jY3NtLnV0aC5lZHUvRnVzaW9uR0RCKQprbm93bl90cmFuc2xvY2F0aW9ucy5GdXNpb25HREIgPC0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbIkZ1c2lvbkdEQiJdXQogIAojIyMjIyBNZXJnZSBpbmZvIGZyb20gYm90aCByZXNvdXJjZXMKa25vd25fdHJhbnNsb2NhdGlvbnMgPC0gbWVyZ2Uoa25vd25fdHJhbnNsb2NhdGlvbnMuRnVzaW9uR0RCLCBrbm93bl90cmFuc2xvY2F0aW9ucy5DR0ksIGJ5LnggPSAiRkduYW1lIiwgYnkueSA9ICJ0cmFuc2xvY2F0aW9uIiwgYWxsID0gVFJVRSwgc29ydD1GQUxTRSkKICAKIyMjIyMgRXh0cmFjdCBnZW5lIHBhaXJzIGludm9sdmVkIGluIHJlcG9ydGVkIGdlbmUgZnVzaW9ucwp0cmFucy5wYWlycyA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKCBrbm93bl90cmFuc2xvY2F0aW9ucyRGR25hbWUsIGtub3duX3RyYW5zbG9jYXRpb25zJEZHbmFtZSApKQpuYW1lcyh0cmFucy5wYWlycykgPC0gYygiZ2VuZUEiLCAiZ2VuZUIiKQp0cmFucy5wYWlycyRnZW5lQSA8LSBzdWIoIl8uKiIsICIiLCB0cmFucy5wYWlycyRnZW5lQSkKdHJhbnMucGFpcnMkZ2VuZUIgPC0gc3ViKCIuKl8iLCAiIiwgdHJhbnMucGFpcnMkZ2VuZUIpCmtub3duX3RyYW5zbG9jYXRpb25zIDwtIGNiaW5kKGtub3duX3RyYW5zbG9jYXRpb25zLCB0cmFucy5wYWlycykKdHJhbnMucGFpcnMgPC0gYXBwbHkoIHRyYW5zLnBhaXJzICwgMSAsIHBhc3RlICwgY29sbGFwc2UgPSAiLSIgKQpgYGAKCmBgYHtyIGFycmliYV9maWx0ZXJpbmcsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1bkFycmliYUNodW5rfQojIyMjIyBSZWFkIGluIHRoZSBhcnJpYmEgZnVzaW9uIGNhbGxzCmFycmliYS5mdXNpb25zIDwtIHJlZl9nZW5lcy5saXN0W1siYXJyaWJhIl1dCmNvbG5hbWVzKGFycmliYS5mdXNpb25zKSA8LSBnc3ViKCJYLmdlbmUxIiwgImdlbmVBIiwgY29sbmFtZXMoYXJyaWJhLmZ1c2lvbnMpKQpjb2xuYW1lcyhhcnJpYmEuZnVzaW9ucykgPC0gZ3N1YigiMSIsICJBIiwgY29sbmFtZXMoYXJyaWJhLmZ1c2lvbnMpKQpjb2xuYW1lcyhhcnJpYmEuZnVzaW9ucykgPC0gZ3N1YigiMiIsICJCIiwgY29sbmFtZXMoYXJyaWJhLmZ1c2lvbnMpKQoKIyMjIyMgIE5vdGUgdGhlIGZ1c2lvbnMgb3JkZXIsIHdoaWNoIHdpbGwgYmUgbGF0ZXIgcmVxdWlyZWQgZm9yIGltYmVkZGluZyBBcnJpYmEgcGxvdHMgZnJvbSBjb3JyZXNwb25kaW5nIHBkZiBib29rbGV0IHBhZ2VzCmFycmliYS5mdXNpb25zLm9yZGVyIDwtIHBhc3RlKGFycmliYS5mdXNpb25zJGdlbmVBLCBhcnJpYmEuZnVzaW9ucyRnZW5lQiwgc2VwPSJfXyIpCgojIyMjIyBFeHRyYWN0IG9ubHkgdGhvc2UgZnVzaW9uIGdlbmVzIHRoYXQgYXJlIGluIGNhbmNlciBnZW5lcyBsaXN0CmFycmliYS5jYW5jZXJfZ2VuZXMgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKHJvdyBpbiAxOm5yb3coYXJyaWJhLmZ1c2lvbnMpKXsKICBpZihhcnJpYmEuZnVzaW9uc1tyb3csImdlbmVBIl0gJWluJSByb3duYW1lcyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSkgfCBhcnJpYmEuZnVzaW9uc1tyb3csImdlbmVCIl0gJWluJSByb3duYW1lcyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSkpIHsKICAgIAogICAgIyMjIyMgQ3JlYXRpbmcgYSBuZXcgZGF0YWZyYW1lIGZvciBleHRyYWN0aW5nIGFycmliYSByb3dzIHdpdGggY2FuY2VyIGdlbmUgaGl0cwogICAgYXJyaWJhLmNhbmNlcl9nZW5lcyA8LSByYmluZChhcnJpYmEuY2FuY2VyX2dlbmVzLCBkYXRhLmZyYW1lKGFycmliYS5mdXNpb25zW3JvdyxdKSkKICB9Cn0KCiMjIyMjIEFkZCBjb2x1bW5zIGZvciBpbmZvIGFib3V0IHJlcG9ydGVkIGZ1c2lvbnMKZnVzaW9ucyA8LSBjYmluZChhcnJpYmEuZnVzaW9ucywgZGF0YS5mcmFtZShtYXRyaXgoIiIsIG5jb2wgPSA1LCBucm93ID0gbnJvdyhhcnJpYmEuZnVzaW9ucykpLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpKQpjb2xuYW1lcyhmdXNpb25zKVsobmNvbChmdXNpb25zKS00KTpuY29sKGZ1c2lvbnMpXSA8LSBjKCJGR0lEIiwgInJlcG9ydGVkX2Z1c2lvbiIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUEiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVCIiwgImVmZmVjdG9yX2dlbmUiKQogIAojIyMjIyBBZGQgYW5ub3RhdGlvbnMgYWJvdXQga25vd24gZnVzaW9uIGV2ZW50cwojIyMjIyBMb29wIHRocm91Z2ggYWxsIGdlbmVzIGludm9sdmVkIGluIGRldGVjZWQgZ2VuZSBmdXNpb25zIChhcnJpYmEgcmVzdWx0cykgYW5kIGNoZWNrIHdoaWNoIGFyZSBhbHJlYWR5IHJlcG9ydGVkCmZvciAoIGkgaW4gMTpucm93KGZ1c2lvbnMpICkgewogIGdlbmVBIDwtIGFzLmNoYXJhY3RlcihmdXNpb25zJGdlbmVBW2ldKQogIGdlbmVCIDwtIGFzLmNoYXJhY3RlcihmdXNpb25zJGdlbmVCW2ldKQogICAgICAgICAgCiAgIyMjIyMgRmlyc3QgY2hlY2sgaWYgdGhlIGV4YWN0IHJlcG9ydGVkIGdlbmUgcGFpcnMgd2VyZSBkZXRlY3RlZCBieSBhcnJpYmEKICBpZiAoIHBhc3RlKGdlbmVBLCBnZW5lQiwgc2VwPSItIikgJWluJSB0cmFucy5wYWlycyApIHsKICAgICAgCiAgICAjIyMjIyBwcm92aWRlIGZ1c2lvbiBVUkwgdG8gRnVzaW9uR0RCCiAgICBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbltpXSA8LSAiWWVzIgogICAgICAKICAgICMjIyMjIHByb3ZpZGUgZnVzaW9uIElEIGZyb20gRnVzaW9uR0RCCiAgICBmdXNpb25zJEZHSURbaV0gPC0ga25vd25fdHJhbnNsb2NhdGlvbnMkRkdJRFsgdHJhbnMucGFpcnMgJWluJSBwYXN0ZShnZW5lQSwgZ2VuZUIsIHNlcD0iLSIpICBdCiAgICAgIAogICAgZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0gPC0gIlllcyIKICAgIGZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCW2ldIDwtICJZZXMiCiAgICAgIAogIH0gZWxzZSBpZiAoIHBhc3RlKGdlbmVCLCBnZW5lQSwgc2VwPSItIikgJWluJSB0cmFucy5wYWlycyApIHsKICAgICAgCiAgICAjIyMjIyBwcm92aWRlIGZ1c2lvbiBVUkwgdG8gRnVzaW9uR0RCCiAgICBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbltpXSA8LSAiWWVzIgogICAgICAKICAgICMjIyMjIHByb3ZpZGUgZnVzaW9uIElEIGZyb20gRnVzaW9uR0RCCiAgICBmdXNpb25zJEZHSURbaV0gPC0ga25vd25fdHJhbnNsb2NhdGlvbnMkRkdJRFsgdHJhbnMucGFpcnMgJWluJSBwYXN0ZShnZW5lQiwgZ2VuZUEsIHNlcD0iLSIpICBdCiAgICAgIAogICAgZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0gPC0gIlllcyIKICAgIGZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCW2ldIDwtICJZZXMiCiAgICAgIAogICMjIyMjIE5vdyBjaGVjayBpZiBhbnkgb2Z0aGUgYXJyaWJhIGRldGVjdGVkIGZ1c2lvbiBnZW5lcyBhcmUgcmVwb3J0ZWQKICB9IGVsc2UgewogICAgZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25baV0gPC0gIi0iCiAgICAgIAogICAgIyMjIyMgQ2hlY2sgdGhlIENhbmNlciBHZW5vbWUgSW50ZXJwcmV0ZXIgKENHSSkgZGF0YWJhc2UgZmlyc3QKICAgICMjIyMjIENoZWNrIGFycmliYSBnZW5lcyBBIGFuZCBnZW5lcyBBIGluIHJlcG9ydGVkIGZ1c2lvbnMKICAgIGlmICggZ2VuZUEgJWluJSBrbm93bl90cmFuc2xvY2F0aW9ucyRnZW5lQSApIHsKICAgICAgIGZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVBW2ldIDwtICJZZXMiCiAgICAgICAgCiAgICAjIyMjIyBDaGVjayBhcnJpYmEgZ2VuZXMgQSBhbmQgZ2VuZXMgQiBpbiByZXBvcnRlZCBmdXNpb25zCiAgICB9IGVsc2UgaWYgKCBnZW5lQSAlaW4lIGtub3duX3RyYW5zbG9jYXRpb25zJGdlbmVCICkgewogICAgICBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQVtpXSA8LSAiWWVzIgogICAgfQogICAgICAKICAgICMjIyMjIENoZWNrIGFycmliYSBnZW5lcyBCIGFuZCBnZW5lcyBBIGluIHJlcG9ydGVkIGZ1c2lvbnMKICAgIGlmICggZ2VuZUIgJWluJSBrbm93bl90cmFuc2xvY2F0aW9ucyRnZW5lQSApIHsKICAgICAgZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUJbaV0gPC0gIlllcyIKICAgICAgICAKICAgICMjIyMjIENoZWNrIGFycmliYSBnZW5lcyBCIGFuZCBnZW5lcyBCIGluIHJlcG9ydGVkIGZ1c2lvbnMKICAgIH0gZWxzZSBpZiAoIGdlbmVCICVpbiUga25vd25fdHJhbnNsb2NhdGlvbnMkZ2VuZUIgKSB7CiAgICAgIGZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCW2ldIDwtICJZZXMiCiAgICB9CiAgICAgIAogICAgIyMjIyMgRmxhZyBpZiBhbnkgb2YgdGhlIGdlbmVzIGFyZSBlZmZlY3RvciBnZW5lCiAgICBpZiAoIGdlbmVBICVpbiUga25vd25fdHJhbnNsb2NhdGlvbnMkZWZmZWN0b3JfZ2VuZSAgKSB7CiAgICAgIGZ1c2lvbnMkZWZmZWN0b3JfZ2VuZVtpXSA8LSBnZW5lQQogICAgfSBlbHNlIGlmICggZ2VuZUIgPT0ga25vd25fdHJhbnNsb2NhdGlvbnMkZWZmZWN0b3JfZ2VuZSAgKSB7CiAgICAgIGZ1c2lvbnMkZWZmZWN0b3JfZ2VuZVtpXSA8LSBnZW5lQgogICAgfQogIH0KfQoKIyMjIyMgU3VtIHNwbGl0IHJlYWRzIGluIGdlbmUgQSBhbmQgQgpmdXNpb25zJHNwbGl0X3JlYWRzIDwtIGZ1c2lvbnMkc3BsaXRfcmVhZHNBICsgZnVzaW9ucyRzcGxpdF9yZWFkc0IKCiMjIyMjIEFkZCBjb2x1bW4gaW5kaWNhdGluZyBmdXNpb25zIGNvbnRhaW5pbmcga25vd24gY2FuY2VyIGdlbmVzCmZ1c2lvbnMkZnVzaW9uc19jYW5jZXIgPC0gYyhyZXAoIi0iLCBucm93KGZ1c2lvbnMpKSkKCmlmICggbnJvdyhhcnJpYmEuY2FuY2VyX2dlbmVzKSA+IDAgKSB7CiAgZnVzaW9ucyRmdXNpb25zX2NhbmNlclsgZnVzaW9ucyRnZW5lQSAlaW4lIGFycmliYS5jYW5jZXJfZ2VuZXMkZ2VuZUEgXSA8LSAiWWVzIgogIGZ1c2lvbnMkZnVzaW9uc19jYW5jZXJbIGZ1c2lvbnMkZ2VuZUIgJWluJSBhcnJpYmEuY2FuY2VyX2dlbmVzJGdlbmVCIF0gPC0gIlllcyIKfQoKIyMjIyMgUmUtb3JkZXJpbmcgYXJyaWJhJ3MgcmVzdWx0cyBvbiB0aGUgYmFzaXMgb2YgQXJyaWJhJ3MgY29uZmlkZW5jZSwgcmVwb3J0ZWQgZnVzaW9ucyBhbmQgdGhlbiByZWFkIGNvdW50IHZhbHVlcyAoZmlyc3QgYnkgc3BsaXQgY291bnQgYW5kIHRoZW4gcGFpcmNvdW50KSBhbmQgdGhlbiBpbnZvbHZtZW50IG9mIGNhbmNlciBnZW5lcyBhbmQgcmVwb3J0ZWQgb25lIG9mIHRoZSBmdXNpb24gZ2VuZXMKZnVzaW9ucyA8LSBmdXNpb25zWyBvcmRlcihmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbiwgZnVzaW9ucyRzcGxpdF9yZWFkcywgZnVzaW9ucyRzcGxpdF9yZWFkc0EsIGZ1c2lvbnMkc3BsaXRfcmVhZHNCLCBmdXNpb25zJGRpc2NvcmRhbnRfbWF0ZXMsIGZ1c2lvbnMkZnVzaW9uc19jYW5jZXIsIGZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVBLCBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQiwgZGVjcmVhc2luZyA9IFRSVUUpLCBdCmZ1c2lvbnMgPC0gZnVzaW9uc1tvcmRlcihmYWN0b3IoZnVzaW9ucyRjb25maWRlbmNlLCBsZXZlbHM9YygiaGlnaCIsICJtZWRpdW0iLCAibG93IikpKSwgXQoKIyMjIyMgS2VlcCBvbmx5IGtleSBjb2x1bW5zIGFuZCBhZGQgaW5mbyBhYm91dCBBcnJpYmEgZGV0ZWN0ZWQgZnVzaW9ucyBhbmQgCmZ1c2lvbnMgPC0gZnVzaW9uc1sgY29sbmFtZXMoZnVzaW9ucykgJWluJSBjKCJnZW5lQSIsICJnZW5lQiIsICJicmVha3BvaW50QSIsICJicmVha3BvaW50QiIsICJzaXRlQSIsICJzaXRlQiIsICJ0eXBlIiwgInNwbGl0X3JlYWRzIiwgInNwbGl0X3JlYWRzQSIsICJzcGxpdF9yZWFkc0IiLCAiZGlzY29yZGFudF9tYXRlcyIsICJjb25maWRlbmNlIiwgIkZHSUQiLCAicmVwb3J0ZWRfZnVzaW9uIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQSIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUIiLCAiZWZmZWN0b3JfZ2VuZSIsICJmdXNpb25zX2NhbmNlciIpXQoKIyMjIyMgQWRkIGNvbHVtbiB0byBmbGFnIGZ1c2lvbnMgc3VwcG9ydGVkIGJ5IFdHUyBkYXRhIChmcm9tIE1BTlRBKSwgaWYgYXZhaWxhYmxlCmZ1c2lvbnMkZ2VuZUFfZG5hX3N1cHBvcnQgPC0gIi0iCmZ1c2lvbnMkZ2VuZUJfZG5hX3N1cHBvcnQgPC0gIi0iCgppZiAoIHJ1blBpenpseUNodW5rIHx8IHJ1bkRyYWdlbkZ1c2lvbkNodW5rICkgewogIGZ1c2lvbnMkQXJyaWJhIDwtIGMocmVwKCJZZXMiLCBucm93KGZ1c2lvbnMpKSkKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CnJtKGFycmliYS5mdXNpb25zLCBhcnJpYmEuZnVzaW9uLnRyYW5zY3JpcHRzLCBhcnJpYmEuY2FuY2VyX2dlbmVzLCBhcnJpYmEub3RoZXJfZ2VuZXMpCmBgYAoKYGBge3IgZHJhZ2VuX2ZpbHRlcmluZywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuRHJhZ2VuRnVzaW9uQ2h1bmt9CiMjIyMjIFJlYWQgaW4gdGhlIGFycmliYSBmdXNpb24gY2FsbHMKZHJhZ2VuLmZ1c2lvbnMgPC0gcmVmX2dlbmVzLmxpc3RbWyJkcmFnZW5GdXNpb24iXV0KY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpIDwtIGdzdWIoImdlbmUxIiwgImdlbmVBIiwgY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpKQpjb2xuYW1lcyhkcmFnZW4uZnVzaW9ucykgPC0gZ3N1YigiMSIsICJBIiwgY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpKQpjb2xuYW1lcyhkcmFnZW4uZnVzaW9ucykgPC0gZ3N1YigiMiIsICJCIiwgY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpKQoKIyMjIyMgRXh0cmFjdCBvbmx5IHRob3NlIGZ1c2lvbiBnZW5lcyB0aGF0IGFyZSBpbiBjYW5jZXIgZ2VuZXMgbGlzdApkcmFnZW4uY2FuY2VyX2dlbmVzIDwtIGRhdGEuZnJhbWUoKQoKZm9yIChyb3cgaW4gMTpucm93KGRyYWdlbi5mdXNpb25zKSl7CiAgaWYoZHJhZ2VuLmZ1c2lvbnNbcm93LCJnZW5lQSJdICVpbiUgcm93bmFtZXMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0pIHwgZHJhZ2VuLmZ1c2lvbnNbcm93LCJnZW5lQiJdICVpbiUgcm93bmFtZXMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0pKSB7CiAgICAKICAgICMjIyMjIENyZWF0aW5nIGEgbmV3IGRhdGFmcmFtZSBmb3IgZXh0cmFjdGluZyBkcmFnZW4gcm93cyB3aXRoIGNhbmNlciBnZW5lIGhpdHMKICAgIGRyYWdlbi5jYW5jZXJfZ2VuZXMgPC0gcmJpbmQoZHJhZ2VuLmNhbmNlcl9nZW5lcywgZGF0YS5mcmFtZShkcmFnZW4uZnVzaW9uc1tyb3csXSkpCiAgfQp9CgojIyMjIyBBZGQgY29sdW1ucyBmb3IgaW5mbyBhYm91dCByZXBvcnRlZCBmdXNpb25zCmRyYWdlbi5mdXNpb25zIDwtIGNiaW5kKGRyYWdlbi5mdXNpb25zLCBkYXRhLmZyYW1lKG1hdHJpeCgiIiwgbmNvbCA9IDUsIG5yb3cgPSBucm93KGRyYWdlbi5mdXNpb25zKSksIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkpCmNvbG5hbWVzKGRyYWdlbi5mdXNpb25zKVsobmNvbChkcmFnZW4uZnVzaW9ucyktNCk6bmNvbChkcmFnZW4uZnVzaW9ucyldIDwtIGMoIkZHSUQiLCAicmVwb3J0ZWRfZnVzaW9uIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQSIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUIiLCAiZWZmZWN0b3JfZ2VuZSIpCgojIyMjIyBBZGQgYW5ub3RhdGlvbnMgYWJvdXQga25vd24gZnVzaW9uIGV2ZW50cwojIyMjIyBMb29wIHRocm91Z2ggYWxsIGdlbmVzIGludm9sdmVkIGluIGRldGVjZWQgZ2VuZSBmdXNpb25zIChkcmFnZW4gcmVzdWx0cykgYW5kIGNoZWNrIHdoaWNoIGFyZSBhbHJlYWR5IHJlcG9ydGVkCmZvciAoIGkgaW4gMTpucm93KGRyYWdlbi5mdXNpb25zKSApIHsKICBnZW5lQSA8LSBhcy5jaGFyYWN0ZXIoZHJhZ2VuLmZ1c2lvbnMkZ2VuZUFbaV0pCiAgZ2VuZUIgPC0gYXMuY2hhcmFjdGVyKGRyYWdlbi5mdXNpb25zJGdlbmVCW2ldKQogICAgICAgICAgCiAgIyMjIyMgRmlyc3QgY2hlY2sgaWYgdGhlIGV4YWN0IHJlcG9ydGVkIGdlbmUgcGFpcnMgd2VyZSBkZXRlY3RlZCBieSBkcmFnZW4KICBpZiAoIHBhc3RlKGdlbmVBLCBnZW5lQiwgc2VwPSItIikgJWluJSB0cmFucy5wYWlycyApIHsKICAgICAgCiAgICAjIyMjIyBwcm92aWRlIGZ1c2lvbiBVUkwgdG8gRnVzaW9uR0RCCiAgICBkcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25baV0gPC0gIlllcyIKICAgICAgCiAgICAjIyMjIyBwcm92aWRlIGZ1c2lvbiBJRCBmcm9tIEZ1c2lvbkdEQgogICAgZHJhZ2VuLmZ1c2lvbnMkRkdJRFtpXSA8LSBrbm93bl90cmFuc2xvY2F0aW9ucyRGR0lEWyB0cmFucy5wYWlycyAlaW4lIHBhc3RlKGdlbmVBLCBnZW5lQiwgc2VwPSItIikgIF0KICAgICAgCiAgICBkcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0gPC0gIlllcyIKICAgIGRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQltpXSA8LSAiWWVzIgogICAgICAKICB9IGVsc2UgaWYgKCBwYXN0ZShnZW5lQiwgZ2VuZUEsIHNlcD0iLSIpICVpbiUgdHJhbnMucGFpcnMgKSB7CiAgICAgIAogICAgIyMjIyMgcHJvdmlkZSBmdXNpb24gVVJMIHRvIEZ1c2lvbkdEQgogICAgZHJhZ2VuLmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uW2ldIDwtICJZZXMiCiAgICAgIAogICAgIyMjIyMgcHJvdmlkZSBmdXNpb24gSUQgZnJvbSBGdXNpb25HREIKICAgIGRyYWdlbi5mdXNpb25zJEZHSURbaV0gPC0ga25vd25fdHJhbnNsb2NhdGlvbnMkRkdJRFsgdHJhbnMucGFpcnMgJWluJSBwYXN0ZShnZW5lQiwgZ2VuZUEsIHNlcD0iLSIpICBdCiAgICAgIAogICAgZHJhZ2VuLmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVBW2ldIDwtICJZZXMiCiAgICBkcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUJbaV0gPC0gIlllcyIKICAgICAgCiAgIyMjIyMgTm93IGNoZWNrIGlmIGFueSBvZiB0aGUgZHJhZ2VuIGRldGVjdGVkIGZ1c2lvbiBnZW5lcyBhcmUgcmVwb3J0ZWQKICB9IGVsc2UgewogICAgZHJhZ2VuLmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uW2ldIDwtICItIgogICAgICAKICAgICMjIyMjIENoZWNrIHRoZSBDYW5jZXIgR2Vub21lIEludGVycHJldGVyIChDR0kpIGRhdGFiYXNlIGZpcnN0CiAgICAjIyMjIyBDaGVjayBkcmFnZW4gZ2VuZXMgQSBhbmQgZ2VuZXMgQiBpbiByZXBvcnRlZCBmdXNpb25zCiAgICBpZiAoIGdlbmVBICVpbiUga25vd25fdHJhbnNsb2NhdGlvbnMkZ2VuZUEgKSB7CiAgICAgICBkcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0gPC0gIlllcyIKICAgICAgICAKICAgICMjIyMjIENoZWNrIGRyYWdlbiBnZW5lcyBBIGFuZCBnZW5lcyBCIGluIHJlcG9ydGVkIGZ1c2lvbnMKICAgIH0gZWxzZSBpZiAoIGdlbmVBICVpbiUga25vd25fdHJhbnNsb2NhdGlvbnMkZ2VuZUIgKSB7CiAgICAgIGRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQVtpXSA8LSAiWWVzIgogICAgfQogICAgICAKICAgICMjIyMjIENoZWNrIGRyYWdlbiBnZW5lcyBCIGFuZCBnZW5lcyBBIGluIHJlcG9ydGVkIGZ1c2lvbnMKICAgIGlmICggZ2VuZUIgJWluJSBrbm93bl90cmFuc2xvY2F0aW9ucyRnZW5lQSApIHsKICAgICAgZHJhZ2VuLmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCW2ldIDwtICJZZXMiCiAgICAgICAgCiAgICAjIyMjIyBDaGVjayBkcmFnZW4gZ2VuZXMgQiBhbmQgZ2VuZXMgQSBpbiByZXBvcnRlZCBmdXNpb25zCiAgICB9IGVsc2UgaWYgKCBnZW5lQiAlaW4lIGtub3duX3RyYW5zbG9jYXRpb25zJGdlbmVCICkgewogICAgICBkcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUJbaV0gPC0gIlllcyIKICAgIH0KICAgICAgCiAgICAjIyMjIyBGbGFnIGlmIGFueSBvZiB0aGUgZ2VuZXMgYXJlIGVmZmVjdG9yIGdlbmUKICAgIGlmICggZ2VuZUEgJWluJSBrbm93bl90cmFuc2xvY2F0aW9ucyRlZmZlY3Rvcl9nZW5lICApIHsKICAgICAgZHJhZ2VuLmZ1c2lvbnMkZWZmZWN0b3JfZ2VuZVtpXSA8LSBnZW5lQQogICAgfSBlbHNlIGlmICggZ2VuZUIgPT0ga25vd25fdHJhbnNsb2NhdGlvbnMkZWZmZWN0b3JfZ2VuZSAgKSB7CiAgICAgIGRyYWdlbi5mdXNpb25zJGVmZmVjdG9yX2dlbmVbaV0gPC0gZ2VuZUIKICAgIH0KICB9Cn0KCiMjIyMjIEFkZCBjb2x1bW4gaW5kaWNhdGluZyBmdXNpb25zIGNvbnRhaW5pbmcga25vd24gY2FuY2VyIGdlbmVzCmRyYWdlbi5mdXNpb25zJGZ1c2lvbnNfY2FuY2VyIDwtIGMocmVwKCItIiwgbnJvdyhkcmFnZW4uZnVzaW9ucykpKQoKaWYgKCBucm93KGRyYWdlbi5jYW5jZXJfZ2VuZXMpID4gMCApIHsKICBkcmFnZW4uZnVzaW9ucyRmdXNpb25zX2NhbmNlclsgZHJhZ2VuLmZ1c2lvbnMkZ2VuZUEgJWluJSBkcmFnZW4uY2FuY2VyX2dlbmVzJGdlbmVBIF0gPC0gIlllcyIKICBkcmFnZW4uZnVzaW9ucyRmdXNpb25zX2NhbmNlclsgZHJhZ2VuLmZ1c2lvbnMkZ2VuZUIgJWluJSBkcmFnZW4uY2FuY2VyX2dlbmVzJGdlbmVCIF0gPC0gIlllcyIKfQoKIyMjIyMgUmUtb3JkZXJpbmcgZHJhZ2VuJ3MgcmVzdWx0cyBvbiB0aGUgYmFzaXMgb2YgRHJhZ2VuJ3MgY29uZmlkZW5jZSwgcmVwb3J0ZWQgZnVzaW9ucyBhbmQgdGhlbiBzY29yZSAoYXMgRHJhZ2VuIGRvZXNuJ3QgaW5jbHVkZXMgc3BsaXQgY291bnQgYW5kIHBhaXJjb3VudCBpbmZvKSBhbmQgdGhlbiBpbnZvbHZtZW50IG9mIGNhbmNlciBnZW5lcyBhbmQgcmVwb3J0ZWQgb25lIG9mIHRoZSBmdXNpb24gZ2VuZXMKZHJhZ2VuLmZ1c2lvbnMgPC0gZHJhZ2VuLmZ1c2lvbnNbIG9yZGVyKGRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbiwgZHJhZ2VuLmZ1c2lvbnMkU2NvcmUsIGRyYWdlbi5mdXNpb25zJGZ1c2lvbnNfY2FuY2VyLCBkcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUEsIGRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQiwgZGVjcmVhc2luZyA9IFRSVUUpLCBdCiNkcmFnZW4uZnVzaW9ucyA8LSBkcmFnZW4uZnVzaW9uc1tvcmRlcihmYWN0b3IoZHJhZ2VuLmZ1c2lvbnMkY29uZmlkZW5jZSwgbGV2ZWxzPWMoImhpZ2giLCAibWVkaXVtIiwgImxvdyIpKSksIF0KCiMjIyMjIEtlZXAgb25seSBrZXkgY29sdW1ucwpkcmFnZW4uZnVzaW9ucyA8LSBkcmFnZW4uZnVzaW9uc1sgY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpICVpbiUgYygiZ2VuZUEiLCAiZ2VuZUIiLCAiU2NvcmUiLCAiTGVmdEJyZWFrcG9pbnQiLCAiUmlnaHRCcmVha3BvaW50IiwgIkdlbmVBTG9jYXRpb24iLCAiR2VuZUJMb2NhdGlvbiIsICJOdW1TcGxpdFJlYWRzIiwgIk51bVNvZnRDbGlwcGVkUmVhZHMiLCAiRkdJRCIsICJyZXBvcnRlZF9mdXNpb24iLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIsICJlZmZlY3Rvcl9nZW5lIiwgImZ1c2lvbnNfY2FuY2VyIildCgojIyMjIyBBZGQgY29sdW1uIHRvIGZsYWcgZnVzaW9ucyBzdXBwb3J0ZWQgYnkgV0dTIGRhdGEgKGZyb20gTUFOVEEpLCBpZiBhdmFpbGFibGUKZHJhZ2VuLmZ1c2lvbnMkZ2VuZUFfZG5hX3N1cHBvcnQgPC0gIi0iCmRyYWdlbi5mdXNpb25zJGdlbmVCX2RuYV9zdXBwb3J0IDwtICItIgoKIyMjIyMgQWRkIHJlc3VsdHMgZnJvbSBBcnJpYmEKaWYgKCBydW5BcnJpYmFDaHVuayApIHsKICAKICAjIyMjIyAgRHJhZ2VuJ3MgZnVzaW9uIGZvcm1hdCB2ZXJzaW9uIDMuOS4zCiAgaWYgKCBhbGwoYygiR2VuZUFMb2NhdGlvbiIsICJHZW5lQkxvY2F0aW9uIiwgIk51bVNwbGl0UmVhZHMiLCJOdW1Tb2Z0Q2xpcHBlZFJlYWRzIiwgIlNjb3JlIikgJWluJSBjb2xuYW1lcyhkcmFnZW4uZnVzaW9ucykpICkgewoKICAgICMjIyMjIEFkZCBjb2x1bW4gd2l0aCBEcmFnZW4gZnVzaW9ucwogICAgZHJhZ2VuLmZ1c2lvbnMkZnVzaW9uIDwtIHBhc3RlKGRyYWdlbi5mdXNpb25zJGdlbmVBLCBkcmFnZW4uZnVzaW9ucyRnZW5lQiwgc2VwPSJfXyIpCiAgICBmdXNpb25zJERyYWdlbiA8LSBjKHJlcCgiLSIsIG5yb3coZnVzaW9ucykpKQogICAgZnVzaW9ucyRzcGxpdF9yZWFkcyA8LSBmdXNpb25zJHNwbGl0X3JlYWRzQSArIGZ1c2lvbnMkc3BsaXRfcmVhZHNCCiAgICBmdXNpb25zJHNvZnRfY2xpcHBlZF9yZWFkcyA8LSBjKHJlcCgiLSIsIG5yb3coZnVzaW9ucykpKQogICAgZnVzaW9ucyRzY29yZSA8LSBjKHJlcCgiLSIsIG5yb3coZnVzaW9ucykpKQogICAgCiAgICAjIyMjIyBSZS1vcmRlciBjb2x1bW5zCiAgICBmdXNpb25zIDwtIGZ1c2lvbnMgJT4lIGRwbHlyOjpyZWxvY2F0ZShzcGxpdF9yZWFkcywgLmJlZm9yZSA9IHNwbGl0X3JlYWRzQSkKICAgIGZ1c2lvbnMgPC0gZnVzaW9ucyAlPiUgZHBseXI6OnJlbG9jYXRlKHNvZnRfY2xpcHBlZF9yZWFkcywgLmJlZm9yZSA9IGNvbmZpZGVuY2UpCiAgICBmdXNpb25zIDwtIGZ1c2lvbnMgJT4lIGRwbHlyOjpyZWxvY2F0ZShzY29yZSwgLmJlZm9yZSA9IEZHSUQpCiAgICAKICAgICMjIyMjIExvb3AgdGhyb3VnaCBEcmFnZW4gcmVzdWx0cywgbWFyayBmdXNpb25zIGRldGVjdGVkIGJ5IGJvdGggdG9vbHMuIEZvciB0aG9zZSBkZXRlY3RlZCBvbmx5IGJ5IERyYWdlbiBhZGFwdCByZXN1bHRzIGZvcm1hdCB0byBBcnJpYmEgcmVzdWx0cwogICAgZm9yICggaSBpbiAxOm5yb3coZHJhZ2VuLmZ1c2lvbnMpICkgewogICAgICAKICAgICAgaWYgKCAhaXMubmEobWF0Y2goZHJhZ2VuLmZ1c2lvbnMkZnVzaW9uW2ldLCBwYXN0ZShmdXNpb25zJGdlbmVBLCBmdXNpb25zJGdlbmVCLCBzZXA9Il9fIikpKSApIHsKICAgICAgICBmdXNpb25zJERyYWdlblsgbWF0Y2goZHJhZ2VuLmZ1c2lvbnMkZnVzaW9uW2ldLCBwYXN0ZShmdXNpb25zJGdlbmVBLCBmdXNpb25zJGdlbmVCLCBzZXA9Il9fIikpIF0gPC0gIlllcyIKICAgICAgICBkcmFnZW4uZnVzaW9uc1sgaSwgXSA8LSAgcmVwKCItIiwgbmNvbChkcmFnZW4uZnVzaW9ucykpCiAgICAgIH0gZWxzZSB7CiAgICAgICAgZnVzaW9ucyA8LSByYmluZChmdXNpb25zLCBkYXRhLmZyYW1lKGdlbmVBPWRyYWdlbi5mdXNpb25zJGdlbmVBW2ldLGdlbmVCPWRyYWdlbi5mdXNpb25zJGdlbmVCW2ldLCBicmVha3BvaW50QT1kcmFnZW4uZnVzaW9ucyRMZWZ0QnJlYWtwb2ludFtpXSwgYnJlYWtwb2ludEI9ZHJhZ2VuLmZ1c2lvbnMkUmlnaHRCcmVha3BvaW50W2ldLCBzaXRlQT1kcmFnZW4uZnVzaW9ucyRHZW5lQUxvY2F0aW9uW2ldLCBzaXRlQj1kcmFnZW4uZnVzaW9ucyRHZW5lQkxvY2F0aW9uW2ldLCB0eXBlPSItIiwgc3BsaXRfcmVhZHM9ZHJhZ2VuLmZ1c2lvbnMkTnVtU3BsaXRSZWFkc1tpXSwgc3BsaXRfcmVhZHNBPSItIiwgc3BsaXRfcmVhZHNCPSItIiwgZGlzY29yZGFudF9tYXRlcz0iLSIsIHNvZnRfY2xpcHBlZF9yZWFkcz1kcmFnZW4uZnVzaW9ucyROdW1Tb2Z0Q2xpcHBlZFJlYWRzW2ldLCBjb25maWRlbmNlPSItIiwgc2NvcmU9ZHJhZ2VuLmZ1c2lvbnMkU2NvcmVbaV0sIEZHSUQ9ZHJhZ2VuLmZ1c2lvbnMkRkdJRFtpXSwgcmVwb3J0ZWRfZnVzaW9uPWRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbltpXSwgcmVwb3J0ZWRfZnVzaW9uX2dlbmVBPWRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQVtpXSwgcmVwb3J0ZWRfZnVzaW9uX2dlbmVCPWRyYWdlbi5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQltpXSwgZWZmZWN0b3JfZ2VuZT1kcmFnZW4uZnVzaW9ucyRlZmZlY3Rvcl9nZW5lW2ldLCBmdXNpb25zX2NhbmNlcj1kcmFnZW4uZnVzaW9ucyRmdXNpb25zX2NhbmNlcltpXSwgZ2VuZUFfZG5hX3N1cHBvcnQ9Ii0iLCBnZW5lQl9kbmFfc3VwcG9ydD0iLSIsIEFycmliYT0iLSIsIERyYWdlbj0iWWVzIiApKQogICAgICB9CiAgICB9CiAgCiAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgcHJpb3IgdG8gdmVyc2lvbiAzLjkuMwogIH0gZWxzZSB7CiAgICAKICAgICMjIyMjIEFkZCBjb2x1bW4gd2l0aCBEcmFnZW4gZnVzaW9ucwogICAgZHJhZ2VuLmZ1c2lvbnMkZnVzaW9uIDwtIHBhc3RlKGRyYWdlbi5mdXNpb25zJGdlbmVBLCBkcmFnZW4uZnVzaW9ucyRnZW5lQiwgc2VwPSJfXyIpCiAgICBmdXNpb25zJERyYWdlbiA8LSBjKHJlcCgiLSIsIG5yb3coZnVzaW9ucykpKQogICAgZnVzaW9ucyRzcGxpdF9yZWFkcyA8LSBmdXNpb25zJHNwbGl0X3JlYWRzQSArIGZ1c2lvbnMkc3BsaXRfcmVhZHNCCiAgICBmdXNpb25zJHNjb3JlIDwtIGMocmVwKCItIiwgbnJvdyhmdXNpb25zKSkpCiAgICAKICAgICMjIyMjIFJlLW9yZGVyIGNvbHVtbnMKICAgIGZ1c2lvbnMgPC0gZnVzaW9ucyAlPiUgZHBseXI6OnJlbG9jYXRlKHNwbGl0X3JlYWRzLCAuYmVmb3JlID0gc3BsaXRfcmVhZHNBKQogICAgZnVzaW9ucyA8LSBmdXNpb25zICU+JSBkcGx5cjo6cmVsb2NhdGUoc2NvcmUsIC5iZWZvcmUgPSBGR0lEKQogICAgICAKICAgICMjIyMjIExvb3AgdGhyb3VnaCBEcmFnZW4gcmVzdWx0cywgbWFyayBmdXNpb25zIGRldGVjdGVkIGJ5IGJvdGggdG9vbHMuIEZvciB0aG9zZSBkZXRlY3RlZCBvbmx5IGJ5IERyYWdlbiBhZGFwdCByZXN1bHRzIGZvcm1hdCB0byBBcnJpYmEgcmVzdWx0cwogICAgZm9yICggaSBpbiAxOm5yb3coZHJhZ2VuLmZ1c2lvbnMpICkgewogICAgICAgIAogICAgICBpZiAoICFpcy5uYShtYXRjaChkcmFnZW4uZnVzaW9ucyRmdXNpb25baV0sIHBhc3RlKGZ1c2lvbnMkZ2VuZUEsIGZ1c2lvbnMkZ2VuZUIsIHNlcD0iX18iKSkpICkgewogICAgICAgIGZ1c2lvbnMkRHJhZ2VuWyBtYXRjaChkcmFnZW4uZnVzaW9ucyRmdXNpb25baV0sIHBhc3RlKGZ1c2lvbnMkZ2VuZUEsIGZ1c2lvbnMkZ2VuZUIsIHNlcD0iX18iKSkgXSA8LSAiWWVzIgogICAgICAgIGRyYWdlbi5mdXNpb25zWyBpLCBdIDwtICByZXAoIi0iLCBuY29sKGRyYWdlbi5mdXNpb25zKSkKICAgICAgfSBlbHNlIHsKICAgICAgICBmdXNpb25zIDwtIHJiaW5kKGZ1c2lvbnMsIGRhdGEuZnJhbWUoZ2VuZUE9ZHJhZ2VuLmZ1c2lvbnMkZ2VuZUFbaV0sZ2VuZUI9ZHJhZ2VuLmZ1c2lvbnMkZ2VuZUJbaV0sIGJyZWFrcG9pbnRBPWRyYWdlbi5mdXNpb25zJExlZnRCcmVha3BvaW50W2ldLCBicmVha3BvaW50Qj1kcmFnZW4uZnVzaW9ucyRSaWdodEJyZWFrcG9pbnRbaV0sIHNpdGVBPSItIiwgc2l0ZUI9Ii0iLCB0eXBlPSItIiwgc3BsaXRfcmVhZHM9Ii0iLCBzcGxpdF9yZWFkc0E9Ii0iLCBzcGxpdF9yZWFkc0I9Ii0iLCBkaXNjb3JkYW50X21hdGVzPSItIiwgY29uZmlkZW5jZT0iLSIsIHNjb3JlPWRyYWdlbi5mdXNpb25zJFNjb3JlW2ldLCBGR0lEPWRyYWdlbi5mdXNpb25zJEZHSURbaV0sIHJlcG9ydGVkX2Z1c2lvbj1kcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25baV0sIHJlcG9ydGVkX2Z1c2lvbl9nZW5lQT1kcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0sIHJlcG9ydGVkX2Z1c2lvbl9nZW5lQj1kcmFnZW4uZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUJbaV0sIGVmZmVjdG9yX2dlbmU9ZHJhZ2VuLmZ1c2lvbnMkZWZmZWN0b3JfZ2VuZVtpXSwgZnVzaW9uc19jYW5jZXI9ZHJhZ2VuLmZ1c2lvbnMkZnVzaW9uc19jYW5jZXJbaV0sIGdlbmVBX2RuYV9zdXBwb3J0PSItIiwgZ2VuZUJfZG5hX3N1cHBvcnQ9Ii0iLCBBcnJpYmE9Ii0iLCBEcmFnZW49IlllcyIgKSkKICAgICAgfQogICAgfQogIH0KICAKIyMjIyMgT3RoZXJ3aXNlIGFkZCBlbXB0eSBjb2x1bW5zIGV4cGVjdGVkIGZyb20gQXJpYmJhIHJlc3VsdHMKfSBlbHNlIHsKICBmdXNpb25zIDwtIGRyYWdlbi5mdXNpb25zCiAgCiAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgdmVyc2lvbiAzLjkuMwogIGlmICggYWxsKGMoIkdlbmVBTG9jYXRpb24iLCAiR2VuZUJMb2NhdGlvbiIsICJOdW1TcGxpdFJlYWRzIiwiTnVtU29mdENsaXBwZWRSZWFkcyIsICJTY29yZSIpICVpbiUgY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpKSApIHsKICAgIAogICAgIyMjIyMgUmVuYW1lIGNvbHVtbnMKICAgIG5hbWVzKGZ1c2lvbnMpIDwtIGdzdWIoIkxlZnRCcmVha3BvaW50IiwgImJyZWFrcG9pbnRBIiwgbmFtZXMoZnVzaW9ucykpCiAgICBuYW1lcyhmdXNpb25zKSA8LSBnc3ViKCJSaWdodEJyZWFrcG9pbnQiLCAiYnJlYWtwb2ludEIiLCBuYW1lcyhmdXNpb25zKSkKICAgIG5hbWVzKGZ1c2lvbnMpIDwtIGdzdWIoIkdlbmVBTG9jYXRpb24iLCAic2l0ZUEiLCBuYW1lcyhmdXNpb25zKSkKICAgIG5hbWVzKGZ1c2lvbnMpIDwtIGdzdWIoIkdlbmVCTG9jYXRpb24iLCAic2l0ZUIiLCBuYW1lcyhmdXNpb25zKSkKICAgIG5hbWVzKGZ1c2lvbnMpIDwtIGdzdWIoIk51bVNwbGl0UmVhZHMiLCAic3BsaXRfcmVhZHMiLCBuYW1lcyhmdXNpb25zKSkKICAgIG5hbWVzKGZ1c2lvbnMpIDwtIGdzdWIoIk51bVNvZnRDbGlwcGVkUmVhZHMiLCAic29mdF9jbGlwcGVkX3JlYWRzIiwgbmFtZXMoZnVzaW9ucykpCiAgICBuYW1lcyhmdXNpb25zKSA8LSBnc3ViKCJTY29yZSIsICJzY29yZSIsIG5hbWVzKGZ1c2lvbnMpKQogIAogICMjIyMjICBEcmFnZW4ncyBmdXNpb24gZm9ybWF0IHByaW9yIHRvIHZlcnNpb24gMy45LjMKICB9IGVsc2UgewogICAgCiAgICAjIyMjIyBSZW5hbWUgY29sdW1ucwogICAgbmFtZXMoZnVzaW9ucykgPC0gZ3N1YigiTGVmdEJyZWFrcG9pbnQiLCAiYnJlYWtwb2ludEEiLCBuYW1lcyhmdXNpb25zKSkKICAgIG5hbWVzKGZ1c2lvbnMpIDwtIGdzdWIoIlJpZ2h0QnJlYWtwb2ludCIsICJicmVha3BvaW50QiIsIG5hbWVzKGZ1c2lvbnMpKQogICAgbmFtZXMoZnVzaW9ucykgPC0gZ3N1YigiU2NvcmUiLCAic2NvcmUiLCBuYW1lcyhmdXNpb25zKSkKICB9Cn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZSBhbmQgcmV0dXJuIG91dHB1dApybShkcmFnZW4uZnVzaW9uLnRyYW5zY3JpcHRzLCBkcmFnZW4uY2FuY2VyX2dlbmVzLCBkcmFnZW4ub3RoZXJfZ2VuZXMpCmBgYAoKYGBge3IgcGl6emx5X2ZpbHRlcmluZywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuUGl6emx5Q2h1bmt9CiMjIyMjIFJlYWQgaW4gdGhlIHBpenpseSBmdXNpb24gY2FsbHMKcGl6emx5LmZ1c2lvbi5jYW5kaWRhdGVzIDwtIHJlZl9nZW5lcy5saXN0W1sicGl6emx5Il1dCgojIyMjIyBFeHRyYWN0IG9ubHkgdGhvc2UgZnVzaW9uIGdlbmVzIHRoYXQgYXJlIGluIGNhbmNlciBnZW5lcyBsaXN0CnBpenpseS5jYW5jZXJfZ2VuZXMgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKHJvdyBpbiAxOm5yb3cocGl6emx5LmZ1c2lvbi5jYW5kaWRhdGVzKSl7CiAgaWYocGl6emx5LmZ1c2lvbi5jYW5kaWRhdGVzW3JvdywiZ2VuZUEubmFtZSJdICVpbiUgcm93bmFtZXMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0pIHwgcGl6emx5LmZ1c2lvbi5jYW5kaWRhdGVzW3JvdywiZ2VuZUIubmFtZSJdICVpbiUgcm93bmFtZXMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0pKSB7CiAgICAKICAgICMjIyMjIENyZWF0aW5nIGEgbmV3IGRhdGFmcmFtZSBmb3IgZXh0cmFjdGluZyBwaXp6bHkgcm93cyB3aXRoIGNhbmNlciBnZW5lIGhpdHMKICAgIHBpenpseS5jYW5jZXJfZ2VuZXMgPC0gcmJpbmQocGl6emx5LmNhbmNlcl9nZW5lcywgZGF0YS5mcmFtZShwaXp6bHkuZnVzaW9uLmNhbmRpZGF0ZXNbcm93LF0pKQogIH0KfQoKIyMjIyMgRXh0cmFjdGluZyByb3dzIGZyb20gcGl6emx5IHJlc3VsdHMgdGhhdCBhcmUgbm90IGNhbmNlciBnZW5lcyBsaXN0CnBpenpseS5vdGhlcl9nZW5lcyA8LSBwaXp6bHkuZnVzaW9uLmNhbmRpZGF0ZXNbIHJvd25hbWVzKHBpenpseS5mdXNpb24uY2FuZGlkYXRlcykgJSFpbiUgcm93bmFtZXMocGl6emx5LmNhbmNlcl9nZW5lcyksIF0KICAKIyMjIyMgQ29tYmluZyBhbGwgdGhlIHRocmVlIGFib3ZlIHNvcnRlZCBkYXRhZnJhbWVzCnBpenpseS5mdXNpb25zIDwtIHJiaW5kKHBpenpseS5jYW5jZXJfZ2VuZXMsIHBpenpseS5vdGhlcl9nZW5lcykKICAKIyMjIyMgRmxhZyBrbm93biBmdXNpb25zIGJhc2VkIG9uIGluZm8gZnJvbSBDYW5jZXIgQmlvbWFya2VycyBkYXRhYmFzZSAoQ0dJKSBhbmQgRnVzaW9uR0RCIChodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpCiMjIyMjIEFkZCBjb2x1bW5zIGZvciBpbmZvIGFib3V0IHJlcG9ydGVkIGZ1c2lvbnMKcGl6emx5LmZ1c2lvbnMgPC0gY2JpbmQocGl6emx5LmZ1c2lvbnMsIGRhdGEuZnJhbWUobWF0cml4KCIiLCBuY29sID0gNSwgbnJvdyA9IG5yb3cocGl6emx5LmZ1c2lvbnMpKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSkKY29sbmFtZXMocGl6emx5LmZ1c2lvbnMpWyhuY29sKHBpenpseS5mdXNpb25zKS00KTpuY29sKHBpenpseS5mdXNpb25zKV0gPC0gYygiRkdJRCIsICJyZXBvcnRlZF9mdXNpb24iLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIsICJlZmZlY3Rvcl9nZW5lIikKICAKIyMjIyMgQWRkIGFubm90YXRpb25zIGFib3V0IGtub3duIGZ1c2lvbiBldmVudHMKIyMjIyMgTG9vcCB0aHJvdWdoIGFsbCBnZW5lcyBpbnZvbHZlZCBpbiBkZXRlY2VkIGdlbmUgZnVzaW9ucyAocGl6emx5IHJlc3VsdHMpIGFuZCBjaGVjayB3aGljaCBhcmUgYWxyZWFkeSByZXBvcnRlZApmb3IgKCBpIGluIDE6bnJvdyhwaXp6bHkuZnVzaW9ucykgKSB7CiAgZ2VuZUEgPC0gYXMuY2hhcmFjdGVyKHBpenpseS5mdXNpb25zJGdlbmVBLm5hbWVbaV0pCiAgZ2VuZUIgPC0gYXMuY2hhcmFjdGVyKHBpenpseS5mdXNpb25zJGdlbmVCLm5hbWVbaV0pCiAgICAgICAgICAKICAjIyMjIyBGaXJzdCBjaGVjayBpZiB0aGUgZXhhY3QgcmVwb3J0ZWQgZ2VuZSBwYWlycyB3ZXJlIGRldGVjdGVkIGJ5IHBpenpseQogIGlmICggcGFzdGUoZ2VuZUEsIGdlbmVCLCBzZXA9Ii0iKSAlaW4lIHRyYW5zLnBhaXJzICkgewogICAgICAKICAgICMjIyMjIHByb3ZpZGUgZnVzaW9uIFVSTCB0byBGdXNpb25HREIKICAgIHBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbltpXSA8LSAiWWVzIgogICAgICAKICAgICMjIyMjIHByb3ZpZGUgZnVzaW9uIElEIGZyb20gRnVzaW9uR0RCCiAgICBwaXp6bHkuZnVzaW9ucyRGR0lEW2ldIDwtIGtub3duX3RyYW5zbG9jYXRpb25zJEZHSURbIHRyYW5zLnBhaXJzICVpbiUgcGFzdGUoZ2VuZUEsIGdlbmVCLCBzZXA9Ii0iKSAgXQogICAgICAKICAgIHBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQVtpXSA8LSAiWWVzIgogICAgcGl6emx5LmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCW2ldIDwtICJZZXMiCiAgICAgIAogIH0gZWxzZSBpZiAoIHBhc3RlKGdlbmVCLCBnZW5lQSwgc2VwPSItIikgJWluJSB0cmFucy5wYWlycyApIHsKICAgICAgCiAgICAjIyMjIyBwcm92aWRlIGZ1c2lvbiBVUkwgdG8gRnVzaW9uR0RCCiAgICBwaXp6bHkuZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25baV0gPC0gIlllcyIKICAgICAgCiAgICAjIyMjIyBwcm92aWRlIGZ1c2lvbiBJRCBmcm9tIEZ1c2lvbkdEQgogICAgcGl6emx5LmZ1c2lvbnMkRkdJRFtpXSA8LSBrbm93bl90cmFuc2xvY2F0aW9ucyRGR0lEWyB0cmFucy5wYWlycyAlaW4lIHBhc3RlKGdlbmVCLCBnZW5lQSwgc2VwPSItIikgIF0KICAgICAgCiAgICBwaXp6bHkuZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0gPC0gIlllcyIKICAgIHBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQltpXSA8LSAiWWVzIgogICAgICAKICAjIyMjIyBOb3cgY2hlY2sgaWYgYW55IG9mdGhlIHBpenpseSBkZXRlY3RlZCBmdXNpb24gZ2VuZXMgYXJlIHJlcG9ydGVkCiAgfSBlbHNlIHsKICAgIHBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbltpXSA8LSAiLSIKICAgICAgCiAgICAjIyMjIyBDaGVjayB0aGUgQ2FuY2VyIEdlbm9tZSBJbnRlcnByZXRlciAoQ0dJKSBkYXRhYmFzZSBmaXJzdAogICAgIyMjIyMgQ2hlY2sgcGl6emx5IGdlbmVzIEEgYW5kIGdlbmVzIEEgaW4gcmVwb3J0ZWQgZnVzaW9ucwogICAgaWYgKCBnZW5lQSAlaW4lIGtub3duX3RyYW5zbG9jYXRpb25zJGdlbmVBICkgewogICAgICAgcGl6emx5LmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVBW2ldIDwtICJZZXMiCiAgICAgICAgCiAgICAjIyMjIyBDaGVjayBwaXp6bHkgZ2VuZXMgQSBhbmQgZ2VuZXMgQiBpbiByZXBvcnRlZCBmdXNpb25zCiAgICB9IGVsc2UgaWYgKCBnZW5lQSAlaW4lIGtub3duX3RyYW5zbG9jYXRpb25zJGdlbmVCICkgewogICAgICBwaXp6bHkuZnVzaW9ucyRyZXBvcnRlZF9mdXNpb25fZ2VuZUFbaV0gPC0gIlllcyIKICAgIH0KICAgICAgCiAgICAjIyMjIyBDaGVjayBwaXp6bHkgZ2VuZXMgQiBhbmQgZ2VuZXMgQSBpbiByZXBvcnRlZCBmdXNpb25zCiAgICBpZiAoIGdlbmVCICVpbiUga25vd25fdHJhbnNsb2NhdGlvbnMkZ2VuZUEgKSB7CiAgICAgIHBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQltpXSA8LSAiWWVzIgogICAgICAgIAogICAgIyMjIyMgQ2hlY2sgcGl6emx5IGdlbmVzIEIgYW5kIGdlbmVzIEIgaW4gcmVwb3J0ZWQgZnVzaW9ucwogICAgfSBlbHNlIGlmICggZ2VuZUIgJWluJSBrbm93bl90cmFuc2xvY2F0aW9ucyRnZW5lQiApIHsKICAgICAgcGl6emx5LmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCW2ldIDwtICJZZXMiCiAgICB9CiAgICAgIAogICAgIyMjIyMgRmxhZyBpZiBhbnkgb2YgdGhlIGdlbmVzIGFyZSBlZmZlY3RvciBnZW5lCiAgICBpZiAoIGdlbmVBICVpbiUga25vd25fdHJhbnNsb2NhdGlvbnMkZWZmZWN0b3JfZ2VuZSAgKSB7CiAgICAgIHBpenpseS5mdXNpb25zJGVmZmVjdG9yX2dlbmVbaV0gPC0gZ2VuZUEKICAgIH0gZWxzZSBpZiAoIGdlbmVCID09IGtub3duX3RyYW5zbG9jYXRpb25zJGVmZmVjdG9yX2dlbmUgICkgewogICAgICBwaXp6bHkuZnVzaW9ucyRlZmZlY3Rvcl9nZW5lW2ldIDwtIGdlbmVCCiAgICB9CiAgfQp9CiAgCiMjIyMjIEFkZCBjb2x1bW4gaW5kaWNhdGluZyBmdXNpb25zIGNvbnRhaW5pbmcga25vd24gY2FuY2VyIGdlbmVzCnBpenpseS5mdXNpb25zJGZ1c2lvbnNfY2FuY2VyIDwtIGMocmVwKCItIiwgbnJvdyhwaXp6bHkuZnVzaW9ucykpKQoKaWYgKCBucm93KHBpenpseS5jYW5jZXJfZ2VuZXMpID4gMCApIHsKICBwaXp6bHkuZnVzaW9ucyRmdXNpb25zX2NhbmNlclsgcGl6emx5LmZ1c2lvbnMkZ2VuZUEubmFtZSAlaW4lIHBpenpseS5jYW5jZXJfZ2VuZXMkZ2VuZUEubmFtZSBdIDwtICJZZXMiCiAgcGl6emx5LmZ1c2lvbnMkZnVzaW9uc19jYW5jZXJbIHBpenpseS5mdXNpb25zJGdlbmVCLm5hbWUgJWluJSBwaXp6bHkuY2FuY2VyX2dlbmVzJGdlbmVCLm5hbWUgXSA8LSAiWWVzIgp9CgojIyMjIyBSZS1vcmRlciBmdXNpb24gZ2VuZXMgYmFzZWQgb24gdGhlIHJlcG9ydGVkIGZ1c2lvbnMgY29sdW1uCnBpenpseS5mdXNpb25zIDwtIHBpenpseS5mdXNpb25zWyBvcmRlcihwaXp6bHkuZnVzaW9ucyRyZXBvcnRlZF9mdXNpb24sIHBpenpseS5mdXNpb25zJHNwbGl0Y291bnQsIHBpenpseS5mdXNpb25zJHBhaXJjb3VudCwgcGl6emx5LmZ1c2lvbnMkZnVzaW9uc19jYW5jZXIsIHBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQSwgcGl6emx5LmZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uX2dlbmVCLCBkZWNyZWFzaW5nID0gVFJVRSksIF0KCiMjIyMjIFJlbmFtZSBjb2x1bW5zIHRvIG1hdGNoIEFycmliYSByZXN1bHRzCmNvbG5hbWVzKHBpenpseS5mdXNpb25zKSA8LSBnc3ViKCJnZW5lQS5uYW1lIiwgImdlbmVBIiwgY29sbmFtZXMocGl6emx5LmZ1c2lvbnMpKQpjb2xuYW1lcyhwaXp6bHkuZnVzaW9ucykgPC0gZ3N1YigiZ2VuZUIubmFtZSIsICJnZW5lQiIsIGNvbG5hbWVzKHBpenpseS5mdXNpb25zKSkKY29sbmFtZXMocGl6emx5LmZ1c2lvbnMpIDwtIGdzdWIoInBhaXJjb3VudCIsICJkaXNjb3JkYW50X21hdGVzIiwgY29sbmFtZXMocGl6emx5LmZ1c2lvbnMpKQpjb2xuYW1lcyhwaXp6bHkuZnVzaW9ucykgPC0gZ3N1Yigic3BsaXRjb3VudCIsICJzcGxpdF9yZWFkcyIsIGNvbG5hbWVzKHBpenpseS5mdXNpb25zKSkKcGl6emx5LmZ1c2lvbnMgPC0gcGl6emx5LmZ1c2lvbnNbIGNvbG5hbWVzKHBpenpseS5mdXNpb25zKSAlIWluJSBjKCJnZW5lQS5pZCIsICJnZW5lQi5pZCIsICJ0cmFuc2NyaXB0cy5saXN0IildCgojIyMjIyBBZGQgcmVzdWx0cyBmcm9tIEFycmliYQppZiAoIHJ1bkFycmliYUNodW5rICkgewogIAogICMjIyMjIEFkZCBjb2x1bW4gd2l0aCBwaXp6bHkgZnVzaW9ucwogIHBpenpseS5mdXNpb25zJGZ1c2lvbiA8LSBwYXN0ZShwaXp6bHkuZnVzaW9ucyRnZW5lQSwgcGl6emx5LmZ1c2lvbnMkZ2VuZUIsIHNlcD0iX18iKQogIGZ1c2lvbnMkUGl6emx5IDwtICBjKHJlcCgiLSIsIG5yb3coZnVzaW9ucykpKQogIAogICMjIyMjIExvb3AgdGhyb3VnaCBQaXp6bHkgcmVzdWx0cywgbWFyayBmdXNpb25zIGRldGVjdGVkIGJ5IGJvdGggdG9vbHMuIEZvciB0aG9zZSBkZXRlY3RlZCBvbmx5IGJ5IHBpenpseSBhZGFwdCByZXN1bHRzIGZvcm1hdCB0byBBcnJpYmEgcmVzdWx0cwogIGZvciAoIGkgaW4gMTpucm93KHBpenpseS5mdXNpb25zKSApIHsKICAgIAogICAgaWYgKCAhaXMubmEobWF0Y2gocGl6emx5LmZ1c2lvbnMkZnVzaW9uW2ldLCBwYXN0ZShmdXNpb25zJGdlbmVBLCBmdXNpb25zJGdlbmVCLCBzZXA9Il9fIikpKSApIHsKICAgICAgZnVzaW9ucyRQaXp6bHlbIG1hdGNoKHBpenpseS5mdXNpb25zJGZ1c2lvbltpXSwgcGFzdGUoZnVzaW9ucyRnZW5lQSwgZnVzaW9ucyRnZW5lQiwgc2VwPSJfXyIpKSBdIDwtICJZZXMiCiAgICAgIHBpenpseS5mdXNpb25zWyBpLCBdIDwtICByZXAoIi0iLCBuY29sKHBpenpseS5mdXNpb25zKSkKICAgIH0gZWxzZSB7CiAgICAgIGZ1c2lvbnMgPC0gcmJpbmQoZnVzaW9ucywgZGF0YS5mcmFtZShnZW5lQT1waXp6bHkuZnVzaW9ucyRnZW5lQVtpXSxnZW5lQj1waXp6bHkuZnVzaW9ucyRnZW5lQltpXSwgYnJlYWtwb2ludEE9Ii0iLCBicmVha3BvaW50Qj0iLSIsIHNpdGVBPSItIiwgc2l0ZUI9Ii0iLCB0eXBlPSItIiwgc3BsaXRfcmVhZHM9cGl6emx5LmZ1c2lvbnMkc3BsaXRfcmVhZHNbaV0sIHNwbGl0X3JlYWRzQT0iLSIsIHNwbGl0X3JlYWRzQj0iLSIsIGRpc2NvcmRhbnRfbWF0ZXM9cGl6emx5LmZ1c2lvbnMkZGlzY29yZGFudF9tYXRlc1tpXSwgY29uZmlkZW5jZT0iLSIsIEZHSUQ9cGl6emx5LmZ1c2lvbnMkRkdJRFtpXSwgcmVwb3J0ZWRfZnVzaW9uPXBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbltpXSwgcmVwb3J0ZWRfZnVzaW9uX2dlbmVBPXBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQVtpXSwgcmVwb3J0ZWRfZnVzaW9uX2dlbmVCPXBpenpseS5mdXNpb25zJHJlcG9ydGVkX2Z1c2lvbl9nZW5lQltpXSwgZWZmZWN0b3JfZ2VuZT1waXp6bHkuZnVzaW9ucyRlZmZlY3Rvcl9nZW5lW2ldLCBmdXNpb25zX2NhbmNlcj1waXp6bHkuZnVzaW9ucyRmdXNpb25zX2NhbmNlcltpXSwgZ2VuZUFfZG5hX3N1cHBvcnQ9Ii0iLCBnZW5lQl9kbmFfc3VwcG9ydD0iLSIsIEFycmliYT0iLSIsIFBpenpseT0iWWVzIiApKQogICAgfQogIH0KIyMjIyMgT3RoZXJ3aXNlIGFkZCBlbXB0eSBjb2x1bW5zIGV4cGVjdGVkIGZyb20gQXJpYmJhIHJlc3VsdHMKfSBlbHNlIHsKICBmdXNpb25zIDwtIHBpenpseS5mdXNpb25zCn0KCiMjIyMjIEFkZCBjb2x1bW4gdG8gZmxhZyBmdXNpb25zIHN1cHBvcnRlZCBieSBXR1MgZGF0YSAoZnJvbSBNQU5UQSksIGlmIGF2YWlsYWJsZQpmdXNpb25zJGdlbmVBX2RuYV9zdXBwb3J0IDwtICItIgpmdXNpb25zJGdlbmVCX2RuYV9zdXBwb3J0IDwtICItIgoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CnJtKHBpenpseS5mdXNpb25zLCBwaXp6bHkuZnVzaW9uLnRyYW5zY3JpcHRzLCBwaXp6bHkuZnVzaW9uLmNhbmRpZGF0ZXMsIGtub3duX3RyYW5zbG9jYXRpb25zLkNHSSwga25vd25fdHJhbnNsb2NhdGlvbnMuRnVzaW9uR0RCLCBwaXp6bHkuY2FuY2VyX2dlbmVzLCBwaXp6bHkub3RoZXJfZ2VuZXMsIHRyYW5zLnBhaXJzKQpgYGAKCmBgYHtyIGZ1c2lvbnNfYW5ub3QsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuRnVzaW9uQ2h1bmt9CiMjIyMjIEFubm90YXRlIGZ1c2lvbiBnZW5lcwojIyMjIyBHZXQgZGF0YSB0byBhbm5vdGF0ZSBmdXNpb24gZ2VuZXMKZnVzaW9uX2dlbmVzX2Fubm90IDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWyAsIGMoIkVOU0VNQkwiLCAiU1lNQk9MIiwgIlNFUU5BTUUiLCAiR0VORVNFUVNUQVJUIiwgIkdFTkVTRVFFTkQiKSBdCgpmdXNpb25zLmFubm90IDwtIGZ1c2lvbnMKZnVzaW9ucy5hbm5vdCRvcmRlciA8LSAxOm5yb3coZnVzaW9ucy5hbm5vdCkKCiMjIyMjIEdldCBnZW5vbWljIGluZm8gZm9yIGZ1c2lvbnMgZ2VuZXMKZnVzaW9uX2Fubm90MSA8LSBtZXJnZShmdXNpb25fZ2VuZXNfYW5ub3QsIGZ1c2lvbnMuYW5ub3RbICwgYygib3JkZXIiLCJnZW5lQSIpXSwgYnkgPSAyLCBzb3J0PUZBTFNFLCBhbGwueSA9IFRSVUUpCmZ1c2lvbl9hbm5vdDEgPC0gZnVzaW9uX2Fubm90MVsgb3JkZXIoZnVzaW9uX2Fubm90MSRvcmRlciksIF0KZnVzaW9uX2Fubm90MiA8LSBtZXJnZShmdXNpb25fZ2VuZXNfYW5ub3QsIGZ1c2lvbnMuYW5ub3RbICwgYygib3JkZXIiLCJnZW5lQiIpXSwgYnkgPSAyLCBzb3J0PUZBTFNFLCBhbGwueSA9IFRSVUUpCmZ1c2lvbl9hbm5vdDIgPC0gZnVzaW9uX2Fubm90Mlsgb3JkZXIoZnVzaW9uX2Fubm90MiRvcmRlciksIF0KCiMjIyMjIERyYWdlbiArIEFycmliYQppZiAoIHJ1bkRyYWdlbkZ1c2lvbkNodW5rICYmIHJ1bkFycmliYUNodW5rICkgewogIGZ1c2lvbl9hbm5vdCA8LSBjYmluZChmdXNpb25fYW5ub3QxLCBmdXNpb25fYW5ub3QyLCBmdXNpb25zLmFubm90WywgYygic2NvcmUiLCAiYnJlYWtwb2ludEEiLCAiYnJlYWtwb2ludEIiLCAiZGlzY29yZGFudF9tYXRlcyIsICJzcGxpdF9yZWFkcyIsICAic3BsaXRfcmVhZHNBIiwgInNwbGl0X3JlYWRzQiIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIpXSkKICAKIyMjIyMgQXJyaWJhIC8gQXJyaWJhICsgUGl6emx5Cn0gZWxzZSBpZiAoIHJ1bkFycmliYUNodW5rICkgewogIGZ1c2lvbl9hbm5vdCA8LSBjYmluZChmdXNpb25fYW5ub3QxLCBmdXNpb25fYW5ub3QyLCBmdXNpb25zLmFubm90WywgYygiYnJlYWtwb2ludEEiLCAiYnJlYWtwb2ludEIiLCAiZGlzY29yZGFudF9tYXRlcyIsICJzcGxpdF9yZWFkcyIsICJzcGxpdF9yZWFkc0EiLCAic3BsaXRfcmVhZHNCIiwgInJlcG9ydGVkX2Z1c2lvbiIsICJmdXNpb25zX2NhbmNlciIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUEiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVCIildKQogIAojIyMjIyBEcmFnZW4gb25seQp9IGVsc2UgaWYgKCBydW5EcmFnZW5GdXNpb25DaHVuayApIHsKICAKICAjIyMjIyAgRHJhZ2VuJ3MgZnVzaW9uIGZvcm1hdCB2ZXJzaW9uIDMuOS4zCiAgaWYgKCBhbGwoYygiR2VuZUFMb2NhdGlvbiIsICJHZW5lQkxvY2F0aW9uIiwgIk51bVNwbGl0UmVhZHMiLCJOdW1Tb2Z0Q2xpcHBlZFJlYWRzIiwgIlNjb3JlIikgJWluJSBjb2xuYW1lcyhkcmFnZW4uZnVzaW9ucykpICkgewogICAgZnVzaW9uX2Fubm90IDwtIGNiaW5kKGZ1c2lvbl9hbm5vdDEsIGZ1c2lvbl9hbm5vdDIsIGZ1c2lvbnMuYW5ub3RbLCBjKCJzY29yZSIsICJicmVha3BvaW50QSIsICJicmVha3BvaW50QiIsICJzcGxpdF9yZWFkcyIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIpXSkKICAgIAogICMjIyMjICBEcmFnZW4ncyBmdXNpb24gZm9ybWF0IHByaW9yIHRvIHZlcnNpb24gMy45LjMKICB9IGVsc2UgewogICAgZnVzaW9uX2Fubm90IDwtIGNiaW5kKGZ1c2lvbl9hbm5vdDEsIGZ1c2lvbl9hbm5vdDIsIGZ1c2lvbnMuYW5ub3RbLCBjKCJzY29yZSIsICJicmVha3BvaW50QSIsICJicmVha3BvaW50QiIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIpXSkKICB9CiAgCiMjIyMjIFBpenpseSBvbmx5Cn0gZWxzZSB7CiAgZnVzaW9uX2Fubm90IDwtIGNiaW5kKGZ1c2lvbl9hbm5vdDEsIGZ1c2lvbl9hbm5vdDIsIGZ1c2lvbnMuYW5ub3RbLCBjKCJzcGxpdF9yZWFkcyIsICJkaXNjb3JkYW50X21hdGVzIiwgInJlcG9ydGVkX2Z1c2lvbiIsICJmdXNpb25zX2NhbmNlciIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUEiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVCIildKQp9CgojIyMjIyBBZGQgY29sdW1uIHRvIGZsYWcgZnVzaW9ucyBzdXBwb3J0ZWQgYnkgV0dTIGRhdGEgKGZyb20gTUFOVEEpLCBpZiBhdmFpbGFibGUKZnVzaW9uX2Fubm90JGdlbmVBX2RuYV9zdXBwb3J0IDwtICItIgpmdXNpb25fYW5ub3QkZ2VuZUJfZG5hX3N1cHBvcnQgPC0gIi0iCgpjb2xuYW1lcyhmdXNpb25fYW5ub3QpID0gbWFrZS5uYW1lcyhjb2xuYW1lcyhmdXNpb25fYW5ub3QpLCB1bmlxdWU9VFJVRSkKCiMjIyMjIFJlbW92ZSBlbnRyaWVzIHdpdGggbWlzc2luZyBhbm5vdGF0aW9uCmZ1c2lvbl9hbm5vdCA8LSBmdXNpb25fYW5ub3RbY29tcGxldGUuY2FzZXMoZnVzaW9uX2Fubm90KSwgXQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGZ1c2lvbl9hbm5vdDEsIGZ1c2lvbl9hbm5vdDIsIGZ1c2lvbnMuYW5ub3QsIGZ1c2lvbl9nZW5lc19hbm5vdCkKYGBgCgpgYGB7ciBmdXNpb25zX2FuZF9tYW50YV9kYXRhX3ByZXAsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blNWc0NodW5rfQojIyMjIyBDb21wYXJlIFBJWlpZIGFuZCBNQU5UQSBjYWxsZWQgZ2VuZSBmdXNpb24gZXZlbnRzCiMjIyMjIEFkZCByb3cgZm9yIGdlbmUgZnVzaW9uIGV2ZW50cyBzbyB0aGF0IHRoZXJlIGlzIG9uZSByb3cgcGVyIGdlbmUKbWFudGFfc3YgPC0gcmVmX2dlbmVzLmxpc3RbWyJtYW50YSJdXQptYW50YV9zdiQiRnVzaW9uIGdlbmVzIiA8LSBtYW50YV9zdiRHZW5lCgppIDwtIDEKd2hpbGUgKCBpIDw9IG5yb3cobWFudGFfc3YpICkgewogIGlmICggbGVuZ3RoKHN0cnNwbGl0KG1hbnRhX3N2JEdlbmVbaV0sIHNwbGl0PScmJywgZml4ZWQ9VFJVRSlbWzFdXSkgPiAxICkgewogICAgIAogICAgIyMjIyMgSW5zZXJ0IG5ldyByb3cgZm9yIGV2ZW50cyBpbnZvbHZpbmcgdHdvIGdlbmVzCiAgICBtYW50YV9zdiA8LSB0aWJibGU6OmFkZF9yb3cobWFudGFfc3YsIC5hZnRlciA9IGkpCiAgICBtYW50YV9zdltpKzEsIF0gPC0gbWFudGFfc3ZbaSwgXQogICAgbWFudGFfc3YkR2VuZVtpXSA8LSBzdHJzcGxpdChtYW50YV9zdiRHZW5lW2ldLCBzcGxpdD0nJicsIGZpeGVkPVRSVUUpW1sxXV1bMV0KICAgIG1hbnRhX3N2JEdlbmVbaSsxXSA8LSBzdHJzcGxpdChtYW50YV9zdiRHZW5lW2krMV0sIHNwbGl0PScmJywgZml4ZWQ9VFJVRSlbWzFdXVsyXQogICAgCiAgICBpIDwtIGkgKyAyCiAgICAKICB9IGVsc2UgewogICAgbWFudGFfc3YkIkZ1c2lvbiBnZW5lcyJbaV0gPC0gIiIKICAgIGkgPC0gaSArIDEKICB9Cn0KCiMjIyMjIENvbXBhcmUgZnVzaW9uIGdlbmVzIGNhbGxlZCBieSBQSVpaTHkgYW5kIE1BTlRBCiMjIyMjIEZpcnN0IGxpbWl0IE1BTlRBIG91dHB1dCB0byBmdXNpb25zIG9ubHkKaWYgKCBydW5GdXNpb25DaHVuayApIHsKICBtYW50YV9mdXNpb25zIDwtIHVuaXF1ZShtYW50YV9zdlsgZ3JlcCgiJiIsIG1hbnRhX3N2JCJGdXNpb24gZ2VuZXMiKSwgIF0kR2VuZSkKICBtYW50YV9mdXNpb25zIDwtIG1hbnRhX2Z1c2lvbnNbIG1hbnRhX2Z1c2lvbnMgJWluJSB1bmlxdWUoYyhhcy52ZWN0b3IoZnVzaW9ucyRnZW5lQSksIGFzLnZlY3RvcihmdXNpb25zJGdlbmVCKSkpIF0KICAgIAogICMjIyMjIEZsYWcgZnVzaW9ucyB0aGF0IHdlcmUgYWxzbyByZXBvcnRlZCBpbiBNQU5UQQogIGlmICggbGVuZ3RoKG1hbnRhX2Z1c2lvbnMpID4gMCApIHsKICAgIGZ1c2lvbnMkZ2VuZUFfZG5hX3N1cHBvcnRbIHNvcnQoIG1hdGNoKCBtYW50YV9mdXNpb25zICwgZnVzaW9ucyRnZW5lQSApLCBuYS5sYXN0ID0gTkEgKSBdIDwtICJZZXMiCiAgICBmdXNpb25zJGdlbmVCX2RuYV9zdXBwb3J0WyBzb3J0KCBtYXRjaCggbWFudGFfZnVzaW9ucyAsIGZ1c2lvbnMkZ2VuZUIgKSwgbmEubGFzdCA9IE5BICkgXSA8LSAiWWVzIgogICAgICAKICAgIGZ1c2lvbl9hbm5vdCRnZW5lQV9kbmFfc3VwcG9ydFsgc29ydCggbWF0Y2goIG1hbnRhX2Z1c2lvbnMgLCBmdXNpb25fYW5ub3QkU1lNQk9MICksIG5hLmxhc3QgPSBOQSApIF0gPC0gIlllcyIKICAgIGZ1c2lvbl9hbm5vdCRnZW5lQl9kbmFfc3VwcG9ydFsgc29ydCggbWF0Y2goIG1hbnRhX2Z1c2lvbnMgLCBmdXNpb25fYW5ub3QkU1lNQk9MLjEgKSwgbmEubGFzdCA9IE5BICkgXSA8LSAiWWVzIgogIAogICAgIyMjIyMgUmUtb3JkZXIgZnVzaW9uIGRhdGFmcmFtZSB3aXRoIE1BTlRBIHN1cHBvcnRpbmcgZnVzaW9ucyBvbiB0b3AKICAgIGlmICggcnVuQXJyaWJhQ2h1bmsgKSB7CiAgICAgIGlkeCA8LSBvcmRlcihmdXNpb25zJGdlbmVBX2RuYV9zdXBwb3J0LCBmdXNpb25zJGdlbmVCX2RuYV9zdXBwb3J0LCBmdXNpb25zJEFycmliYSwgZnVzaW9ucyRyZXBvcnRlZF9mdXNpb24sIGRlY3JlYXNpbmcgPSBUUlVFKQogICAgfSBlbHNlIHsKICAgICAgaWR4IDwtIG9yZGVyKGZ1c2lvbnMkZ2VuZUFfZG5hX3N1cHBvcnQsIGZ1c2lvbnMkZ2VuZUJfZG5hX3N1cHBvcnQsIGZ1c2lvbnMkcmVwb3J0ZWRfZnVzaW9uLCBkZWNyZWFzaW5nID0gVFJVRSkKICAgIH0KICAgIAogICAgZnVzaW9ucyA8LSBmdXNpb25zWyBpZHgsIF0KICAgIGZ1c2lvbl9hbm5vdCA8LSBmdXNpb25fYW5ub3RbIGlkeCwgXQogIH0KfQoKIyMjIyMgUmVtb3ZlIGVudHJpZXMgd2l0aCBtaXNzaW5nIGFubm90YXRpb24KZnVzaW9uX2Fubm90IDwtIGZ1c2lvbl9hbm5vdFtjb21wbGV0ZS5jYXNlcyhmdXNpb25fYW5ub3QpLCBdCgojIyMjIyBDbGVhbiB0aGUgc3BhY2UgYW5kIHJldHVybiBvdXRwdXQKcm0obWFudGFfZnVzaW9ucykKYGBgCgpgYGB7ciBmdXNpb25zX2ZpbHRlcmluZywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuRnVzaW9uQ2h1bmt9CiMjIyMjIEZpbHRlciBvdXQgZnVzaW9ucyB0aGF0IGFyZSB3aXRoIDwgMiBzcGxpdCByZWFkcyBhbmQgPCAyIHBhaXIgcmVhZHMgYW5kIGFyZSBub3Qgc3VwcG9ydGVkIGJ5IGdlbm9taWMgZGF0YSwgYXJlIG5vdCByZXBvcnRlZCBhbmQgZG9uJ3QgaW52b2x2ZSBjYW5jZXIgZ2VuZXMKCiMjIyMjIERyYWdlbiArIEFycmliYQppZiAoIHJ1bkRyYWdlbkZ1c2lvbkNodW5rICYmIHJ1bkFycmliYUNodW5rICkgewogIGZ1c2lvbnMgPC0gZnVzaW9ucyAlPiUgZHBseXI6OmZpbHRlciggc3BsaXRfcmVhZHMgPiAxIHwgZGlzY29yZGFudF9tYXRlcyA+IDEgfCBzY29yZSA+IDAgfCBnZW5lQV9kbmFfc3VwcG9ydCAhPSAiLSIgfCBnZW5lQl9kbmFfc3VwcG9ydCAhPSAiLSIgfCByZXBvcnRlZF9mdXNpb24gIT0gIi0iIHwgZnVzaW9uc19jYW5jZXIgIT0gIi0iKQogIGZ1c2lvbl9hbm5vdCA8LSBmdXNpb25fYW5ub3QgJT4lIGRwbHlyOjpmaWx0ZXIoIHNwbGl0X3JlYWRzID4gMSB8IGRpc2NvcmRhbnRfbWF0ZXMgPiAxIHwgZ2VuZUFfZG5hX3N1cHBvcnQgIT0gIi0iIHwgZ2VuZUJfZG5hX3N1cHBvcnQgIT0gIi0iIHwgcmVwb3J0ZWRfZnVzaW9uICE9ICItIiB8IGZ1c2lvbnNfY2FuY2VyICE9ICItIikKCiAgIyMjIyMgQXJyaWJhIC8gQXJyaWJhICsgUGl6emx5Cn0gZWxzZSBpZiAoIHJ1bkFycmliYUNodW5rICkgewogIGZ1c2lvbnMgPC0gZnVzaW9ucyAlPiUgZHBseXI6OmZpbHRlciggc3BsaXRfcmVhZHMgPiAxIHwgZGlzY29yZGFudF9tYXRlcyA+IDEgfCBnZW5lQV9kbmFfc3VwcG9ydCAhPSAiLSIgfCBnZW5lQl9kbmFfc3VwcG9ydCAhPSAiLSIgfCByZXBvcnRlZF9mdXNpb24gIT0gIi0iIHwgZnVzaW9uc19jYW5jZXIgIT0gIi0iKQogIGZ1c2lvbl9hbm5vdCA8LSBmdXNpb25fYW5ub3QgJT4lIGRwbHlyOjpmaWx0ZXIoIHNwbGl0X3JlYWRzID4gMSB8IGRpc2NvcmRhbnRfbWF0ZXMgPiAxIHwgZ2VuZUFfZG5hX3N1cHBvcnQgIT0gIi0iIHwgZ2VuZUJfZG5hX3N1cHBvcnQgIT0gIi0iIHwgcmVwb3J0ZWRfZnVzaW9uICE9ICItIiB8IGZ1c2lvbnNfY2FuY2VyICE9ICItIikKICAKIyMjIyMgRHJhZ2VuIG9ubHkKfSBlbHNlIGlmICggcnVuRHJhZ2VuRnVzaW9uQ2h1bmsgKSB7CiAgCiAgIyMjIyMgRm9yIERyYWdlbiAsIHRoaXMgZmlsdGVyaW5nIGlzIG5vdCBjaGFuZ2luZyB0aGUgcmVzdWx0cy4gV2UnbGwgcmV2aWV3IHRoZSAiU2NvcmUiIHZhbHVlIGFnYWluIG9uY2Ugd2Ugc3RhcnQgdG8gcmVndWxhcmx5IHByb2R1Y2UgdGhlIFJOQXN1bSByZXBvcnQgZm9yIERyYWdlbiByZXN1bHRzCiAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgdmVyc2lvbiAzLjkuMwogIGlmICggYWxsKGMoIkdlbmVBTG9jYXRpb24iLCAiR2VuZUJMb2NhdGlvbiIsICJOdW1TcGxpdFJlYWRzIiwiTnVtU29mdENsaXBwZWRSZWFkcyIsICJTY29yZSIpICVpbiUgY29sbmFtZXMoZnVzaW9ucykpICkgewogICAgZnVzaW9ucyA8LSBmdXNpb25zICU+JSBkcGx5cjo6ZmlsdGVyKCBzcGxpdF9yZWFkcyA+IDEgfCBzY29yZSA+IDAgfCBnZW5lQV9kbmFfc3VwcG9ydCAhPSAiLSIgfCBnZW5lQl9kbmFfc3VwcG9ydCAhPSAiLSIgfCByZXBvcnRlZF9mdXNpb24gIT0gIi0iIHwgZnVzaW9uc19jYW5jZXIgIT0gIi0iKQogICAgZnVzaW9uX2Fubm90IDwtIGZ1c2lvbl9hbm5vdCAlPiUgZHBseXI6OmZpbHRlciggc2NvcmUgPiAxIHwgZ2VuZUFfZG5hX3N1cHBvcnQgIT0gIi0iIHwgZ2VuZUJfZG5hX3N1cHBvcnQgIT0gIi0iIHwgcmVwb3J0ZWRfZnVzaW9uICE9ICItIiB8IGZ1c2lvbnNfY2FuY2VyICE9ICItIikKICAKICAjIyMjIyAgRHJhZ2VuJ3MgZnVzaW9uIGZvcm1hdCBwcmlvciB0byB2ZXJzaW9uIDMuOS4zCiAgfSBlbHNlIHsKICAgIGZ1c2lvbnMgPC0gZnVzaW9ucyAlPiUgZHBseXI6OmZpbHRlciggc2NvcmUgPiAwIHwgZ2VuZUFfZG5hX3N1cHBvcnQgIT0gIi0iIHwgZ2VuZUJfZG5hX3N1cHBvcnQgIT0gIi0iIHwgcmVwb3J0ZWRfZnVzaW9uICE9ICItIiB8IGZ1c2lvbnNfY2FuY2VyICE9ICItIikKICAgIGZ1c2lvbl9hbm5vdCA8LSBmdXNpb25fYW5ub3QgJT4lIGRwbHlyOjpmaWx0ZXIoIHNjb3JlID4gMSB8IGdlbmVBX2RuYV9zdXBwb3J0ICE9ICItIiB8IGdlbmVCX2RuYV9zdXBwb3J0ICE9ICItIiB8IHJlcG9ydGVkX2Z1c2lvbiAhPSAiLSIgfCBmdXNpb25zX2NhbmNlciAhPSAiLSIpCiAgfQogIAojIyMjIyBQaXp6bHkgb25seQp9IGVsc2UgewogIGZ1c2lvbnMgPC0gZnVzaW9ucyAlPiUgZHBseXI6OmZpbHRlcihzcGxpdF9yZWFkcyA+IDEgfCBkaXNjb3JkYW50X21hdGVzID4gMSB8IGdlbmVBX2RuYV9zdXBwb3J0ICE9ICItIiB8IGdlbmVCX2RuYV9zdXBwb3J0ICE9ICItIiB8IHJlcG9ydGVkX2Z1c2lvbiAhPSAiLSIgfCBmdXNpb25zX2NhbmNlciAhPSAiLSIpCiAgZnVzaW9uX2Fubm90IDwtIGZ1c2lvbl9hbm5vdCAlPiUgZHBseXI6OmZpbHRlcihzcGxpdF9yZWFkcyA+IDEgfCBkaXNjb3JkYW50X21hdGVzID4gMSB8IGdlbmVBX2RuYV9zdXBwb3J0ICE9ICItIiB8IGdlbmVCX2RuYV9zdXBwb3J0ICE9ICItIiB8IHJlcG9ydGVkX2Z1c2lvbiAhPSAiLSIgfCBmdXNpb25zX2NhbmNlciAhPSAiLSIpCn0KYGBgCgpgYGB7ciBmdXNpb25zX2NpcmNvcywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuRnVzaW9uQ2h1bmt9CiMjIyMjIEluZGljYXRlIHdoaWNoIGZ1c2lvbnMgaGF2ZSBnZW5vbWljIGNvb3JkaW5hdGVzIGFuZCBjYW4gYmUgcHJlc2VudGVkIG9uIGNpcmNvcyBwbG90CiMjIyMjIFRha2UgaW50byBhY2NvdW50IG9ubHkgcmVwb3J0ZWQgZnVzaW9ucyBvciB0aG9zZSB3aXRoIGJvdGggZ2VuZXMgZ2VuZXMgc3VwcG9ydGVkIGJ5IEROQQppZiAoIHJ1blNWc0NodW5rICkgewogIGZ1c2lvbl9hbm5vdF90b3AgPC0gZnVzaW9uX2Fubm90WyBmdXNpb25fYW5ub3QkcmVwb3J0ZWRfZnVzaW9uID09ICJZZXMiIHwgZnVzaW9uX2Fubm90JGdlbmVBX2RuYV9zdXBwb3J0ID09ICJZZXMiIHwgZnVzaW9uX2Fubm90JGdlbmVCX2RuYV9zdXBwb3J0ID09ICJZZXMiICwgXQp9IGVsc2UgewogIGZ1c2lvbl9hbm5vdF90b3AgPC0gZnVzaW9uX2Fubm90WyBmdXNpb25fYW5ub3QkcmVwb3J0ZWRfZnVzaW9uID09ICJZZXMiICwgXQp9CgpmdXNpb25zJGNpcmNvcyA8LSAiLSIKZnVzaW9ucyRjaXJjb3NbIHBhc3RlKGZ1c2lvbnMkZ2VuZUEsIGZ1c2lvbnMkZ2VuZUIsIHNlcD0iLSIpICVpbiUgcGFzdGUoZnVzaW9uX2Fubm90X3RvcCRTWU1CT0wsIGZ1c2lvbl9hbm5vdF90b3AkU1lNQk9MLjEsIHNlcD0iLSIpIF0gPC0gIlllcyIKYGBgCgpgYGB7ciBpbW11bm9ncmFtX3RhYmxlX3ByZXAsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHBhcmFtcyRpbW11bm9ncmFtfQojIyMjIyBFeHRyYWN0IGRhdGEgZm9yIEltbXVub2dyYW0gZ2VuZXMKZGF0YSA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXQpkYXRhIDwtIGRhdGFbIHJvd25hbWVzKGRhdGEpICVpbiUgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbSRTWU1CT0wsIF0KCiMjIyMjIENyZWF0ZSBsaXN0cyB3aXRoIGNhdWxjdWF0aW9uIHJlc3VsdHMgZm9yIGVhY2ggaW5kaXZpZHVhbCBDYW5jZXItSW1tdW5pdHkgQ3ljbGUgKENJQykgc3RlcApDSUMubGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgodW5pcXVlKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0kQ0lDKSkpCm5hbWVzKENJQy5saXN0KSA8LSB1bmlxdWUocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbSRDSUMpCiAgCiMjIyMjIENhbGN1bGF0ZSBhdmVyYWdlIGV4cHJlc3Npb24gZm9yIGVhY2ggQ2FuY2VyLUltbXVuaXR5IEN5Y2xlIChDSUMpIHN0ZXAKZm9yICggY2ljX3N0ZXAgaW4gdW5pcXVlKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0kQ0lDKSApIHsKICAKICBnZW5lcyA8LSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bm9ncmFtJFNZTUJPTFsgcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbSRDSUMgJWluJSBjaWNfc3RlcCBdCiAgZGF0YS5zdWIgPC0gZGF0YVsgcm93bmFtZXMoZGF0YSkgJWluJSBnZW5lcywgXQogIENJQy5saXN0W1tjaWNfc3RlcF1dIDwtIGNvbE1lYW5zKGRhdGEuc3ViKQp9CgojIyMjIyBDb252ZXIgdGhlIGxpc3QgaW50byBkYXRhZnJhbWUKcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbS5kZiA8LSB0KGRhdGEuZnJhbWUobWF0cml4KHVubGlzdChDSUMubGlzdCksIG5yb3c9bGVuZ3RoKENJQy5saXN0KSwgYnlyb3c9VCksc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkpCmNvbG5hbWVzKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0uZGYpIDwtIG5hbWVzKENJQy5saXN0KQpyb3duYW1lcyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2ltbXVuZSJdXSRpbW11bm9ncmFtLmRmKSA8LSBjb2xuYW1lcyhkYXRhLnN1YikKYGBgCgpgYGB7ciByZWZfY29ob3J0c19zdW1tYXJ5LCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIFN1bW1hcmlzZSB0aGUgcmVmZXJlbmNlIGNvaG9ydHMgc2FtcGxlcwp0YXJnZXQgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sic2FtcGxlX2Fubm90Il1dCnJlZl9leHRfY2FuY2VyIDwtIHRhYmxlKHRhcmdldCRUYXJnZXQpW25hbWVzKHRhYmxlKHRhcmdldCRUYXJnZXQpKT09ZXh0X2NhbmNlcl9ncm91cF0KcmVmX2ludF9jYW5jZXIgPC0gdGFibGUodGFyZ2V0JFRhcmdldClbbmFtZXModGFibGUodGFyZ2V0JFRhcmdldCkpPT1pbnRfY2FuY2VyX2dyb3VwXQoKaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyX2dyb3VwKSApIHsKICByZWZfZXh0X2NhbmNlciA8LSB0YWJsZSh0YXJnZXQkVGFyZ2V0KVtuYW1lcyh0YWJsZSh0YXJnZXQkVGFyZ2V0KSk9PWMoZXh0X2NhbmNlcl9ncm91cCldICsgIHRhYmxlKHRhcmdldCRUYXJnZXQpW25hbWVzKHRhYmxlKHRhcmdldCRUYXJnZXQpKT09YyhhZGRfY2FuY2VyX2dyb3VwKV0KfQpgYGAKCmBgYHtyIGdvaV9zdW1tYXJ5X3VwZGF0ZSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBVcGRhdGUgYWx0ZXJlZCBnZW5lcyBpbiAuLi4KIyMjIyMgLi4uZ2VuZSBmdXNpb24gc2VjdGlvbjogSW5jbHVkZSBvbmx5IHRob3NlIHdoaWNoIGFyZSBETkEtc3VwcG9ydGVkIChzZWUgU3RydWN0dXJhbCB2YXJpYW50cyBzZWN0aW9uKSBvciByZXBvcnRlZCBpbiBGdXNpb25HREIgCmlmICggcnVuRnVzaW9uQ2h1bmsgKSB7CiAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEZ1c2lvbiA8LSBmdXNpb25zWyBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbiA9PSAiWWVzIiB8IGZ1c2lvbnMkZ2VuZUFfZG5hX3N1cHBvcnQgPT0gIlllcyIgfCBmdXNpb25zJGdlbmVCX2RuYV9zdXBwb3J0ID09ICJZZXMiICwgXQogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRGdXNpb24gPC0gdW5pcXVlKGMoYXMuY2hhcmFjdGVyKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRGdXNpb24kZ2VuZUEpLCBhcy5jaGFyYWN0ZXIocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEZ1c2lvbiRnZW5lQikpKQp9IGVsc2UgewogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRGdXNpb24gPC0gTlVMTAp9CgojIyMjIyAuLi5jb3B5LW51bWJlciAoQ04pIHNlY3Rpb246IGluY2x1ZGUgb25seSBnZW5lcyB3aXRoIENOIHZhbHVlcyA+IDMgb3IgPCAwLjUKaWYgKCBydW5QdXJwbGVDaHVuayApIHsKICAKICAjIyMjIEtlZXAgb25seSBnZW5lcyB3aXRoIHVzZXItZGVmaW5lIENOIHZhbHVlcwogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJleHByX211dF9jbl9kYXRhIl1dCiAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJENOIDwtIGFzLmNoYXJhY3RlcihyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ05bIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRDTiRDTiA8PSBjbl9ib3R0b20gfCByZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kQ04kQ04gPj0gY25fdG9wLCAgXSRHZW5lKQp9CgojIyMjIyAuLi5pbW11bmUgbWFya2VycyBzZWN0aW9uOiBpbmNsdWRlIG9ubHkgZ2VuZXMgd2l0aCBhdmFpbGFibGUgYW5ub3RhdGlvbgppZiAoIHBhcmFtcyRpbW11bm9ncmFtICkgewogIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRJbW11bmUgPC0gdW5pcXVlKGMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbSRTWU1CT0wsIHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzJFNZTUJPTCkpCn0gZWxzZSB7CiAgcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJEltbXVuZSA8LSB1bmlxdWUocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5lX21hcmtlcnMkU1lNQk9MKQp9CmBgYAoKYGBge3IgZ29pX3N1bmJ1cnN0LCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQoKIyMjIyMgUHJlcGFyZSBkYXRhZnJhbWUgZm9yIFN1bmJ1cnN0IHBsb3Qgc3VtbWFyaXNpbmcgYWxsIGFsdGVyZWQgZ2VuZXMKYWx0X2dlbmVzLmFsbC5saXN0IDwtIHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXQoKIyMjIyMgRG9uJ3Qgc2hvdyBjYW5jZXIgZ2VuZXMgbGlzdCAodG9vIGxvbmcpCmFsdF9nZW5lcy5hbGwubGlzdCRDYW5jZXIgPC0gTlVMTAoKIyMjIyMgTm90ZSBhbGwgYWx0ZXJlZCBnZW5lcwphbHRfZ2VuZXMuYWxsIDwtIHNvcnQodGFibGUodW5saXN0KGFsdF9nZW5lcy5hbGwubGlzdCkpLCBkZWNyZWFzaW5nID0gVFJVRSkKCmZvciAoIGFsdCBpbiBuYW1lcyhhbHRfZ2VuZXMuYWxsLmxpc3QpICkgewoKICAjIyMjIyBBZGQgb25seSBhbHRlcmF0aW9uIHR5cGUgd2hpY2ggaGFzIGF0IGxlYXN0IG9uZSBhbHRlcmF0aW9uIGRldGVjdGVkCiAgaWYgKCBsZW5ndGgoYWx0X2dlbmVzLmFsbFsgbmFtZXMoYWx0X2dlbmVzLmFsbCkgJWluJSBhbHRfZ2VuZXMuYWxsLmxpc3RbWyBhbHQgXV0gXSkgID4gMCApIHsKICAgIGFsdF9nZW5lcy5hbGwubGlzdFtbIGFsdCBdXSA8LSBhbHRfZ2VuZXMuYWxsWyBuYW1lcyhhbHRfZ2VuZXMuYWxsKSAlaW4lIGFsdF9nZW5lcy5hbGwubGlzdFtbIGFsdCBdXSBdCiAgfSBlbHNlIHsKICAgIGFsdF9nZW5lcy5hbGwubGlzdFtbIGFsdCBdXSA8LSBOVUxMCiAgfQp9CgpzdW5idXJzdC5hbGwuZGYgPC0gZGF0YS5mcmFtZShpZHMgPSBuYW1lcyhhbHRfZ2VuZXMuYWxsLmxpc3QpLAogIGxhYmVscyA9IG5hbWVzKGFsdF9nZW5lcy5hbGwubGlzdCksCiAgcGFyZW50cyA9IHJlcCgiIiwgbGVuZ3RoKGFsdF9nZW5lcy5hbGwubGlzdCkpLAogIHZhbHVlcyA9IGFzLm51bWVyaWMobGVuZ3RocyhhbHRfZ2VuZXMuYWxsLmxpc3QpKS8xMDAsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikKCmZvciAoIGFsdCBpbiBuYW1lcyhhbHRfZ2VuZXMuYWxsLmxpc3QpICkgewogIAogICMjIyMjIEFkZCBvbmx5IGFsdGVyYXRpb24gdHlwZSB3aGljaCBoYXMgYXQgbGVhc3Qgb25lIGFsdGVyYXRpb24gZGV0ZWN0ZWQKICBpZiAoIGxlbmd0aChhbHRfZ2VuZXMuYWxsWyBuYW1lcyhhbHRfZ2VuZXMuYWxsKSAlaW4lIG5hbWVzKGFsdF9nZW5lcy5hbGwubGlzdFtbIGFsdCBdXSkgXSkgID4gMCApIHsKICAgICAgCiAgICBzdW5idXJzdC5hbGwuZGYgPC0gcmJpbmQoIHN1bmJ1cnN0LmFsbC5kZiAsIGRhdGEuZnJhbWUoaWRzID0gcGFzdGUoIGFsdCwgbmFtZXMoYWx0X2dlbmVzLmFsbC5saXN0W1sgYWx0IF1dKSwgc2VwID0gIiAtICIpLCAKICAgICAgICAgIGxhYmVscyA9IHBhc3RlMCgiXHRcdCIsIG5hbWVzKGFsdF9nZW5lcy5hbGwubGlzdFtbIGFsdCBdXSksICJcdFx0IiksCiAgICAgICAgICBwYXJlbnRzID0gcmVwKCBhbHQgLCBsZW5ndGgoYWx0X2dlbmVzLmFsbC5saXN0W1sgYWx0IF1dKSksCiAgICAgICAgICB2YWx1ZXMgPSBhcy5udW1lcmljKGFsdF9nZW5lcy5hbGwubGlzdFtbIGFsdCBdXSkKICAgICAgICAgICkgKQogIH0KfQoKc3VuYnVyc3RfcGxvdCA8LSBOVUxMCnN1bmJ1cnN0X3Bsb3RbWzFdXSA8LSBwbG90X2x5KHN1bmJ1cnN0LmFsbC5kZiwgaWRzID0gfmlkcywgbGFiZWxzID0gfmxhYmVscywgcGFyZW50cyA9IH5wYXJlbnRzLCB2YWx1ZXMgPSB+dmFsdWVzLCB0eXBlID0gJ3N1bmJ1cnN0Jywgd2lkdGggPSA2MDAsIGhlaWdodCA9IDYwMCkKCiMjIyMjIE5vdyBpbmNsdWRlIG9ubHkgSWRlbnRpZnkgZ2VuZXMgdGhhdCBhcHBlYXIgaW4gbW9yZSB0aGVuIHR3byBsaXN0cwphbHRfZ2VuZXMubGlzdCA8LSBhbHRfZ2VuZXMuYWxsLmxpc3QKYWx0X2dlbmVzIDwtIGFsdF9nZW5lcy5hbGxbIGFsdF9nZW5lcy5hbGwgPiAxIF0KCmZvciAoIGFsdCBpbiBuYW1lcyhhbHRfZ2VuZXMubGlzdCkgKSB7CgogICMjIyMjIEFkZCBvbmx5IGFsdGVyYXRpb24gdHlwZSB3aGljaCBoYXMgYXQgbGVhc3Qgb25lIGFsdGVyYXRpb24gZGV0ZWN0ZWQKICBpZiAoIGxlbmd0aChhbHRfZ2VuZXNbIG5hbWVzKGFsdF9nZW5lcykgJWluJSBuYW1lcyhhbHRfZ2VuZXMubGlzdFtbIGFsdCBdXSkgXSkgID4gMCApIHsKICAgIGFsdF9nZW5lcy5saXN0W1sgYWx0IF1dIDwtIGFsdF9nZW5lc1sgbmFtZXMoYWx0X2dlbmVzKSAlaW4lIG5hbWVzKGFsdF9nZW5lcy5saXN0W1sgYWx0IF1dKSBdCiAgfSBlbHNlIHsKICAgIGFsdF9nZW5lcy5saXN0W1sgYWx0IF1dIDwtIE5VTEwKICB9Cn0KICAKc3VuYnVyc3QuZGYgPC0gZGF0YS5mcmFtZShpZHMgPSBuYW1lcyhhbHRfZ2VuZXMubGlzdCksCiAgbGFiZWxzID0gbmFtZXMoYWx0X2dlbmVzLmxpc3QpLAogIHBhcmVudHMgPSByZXAoIiIsIGxlbmd0aChhbHRfZ2VuZXMubGlzdCkpLAogIHZhbHVlcyA9IGFzLm51bWVyaWMobGVuZ3RocyhhbHRfZ2VuZXMubGlzdCkpLzEwMCwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQogIApmb3IgKCBhbHQgaW4gbmFtZXMoYWx0X2dlbmVzLmxpc3QpICkgewogIHN1bmJ1cnN0LmRmIDwtIHJiaW5kKCBzdW5idXJzdC5kZiAsIGRhdGEuZnJhbWUoaWRzID0gcGFzdGUoIGFsdCwgbmFtZXMoYWx0X2dlbmVzLmxpc3RbWyBhbHQgXV0pLCBzZXAgPSAiIC0gIiksIAogICAgICAgIGxhYmVscyA9IHBhc3RlMCgiXHRcdCIsIG5hbWVzKGFsdF9nZW5lcy5saXN0W1sgYWx0IF1dKSwgIlx0XHQiKSwKICAgICAgICBwYXJlbnRzID0gcmVwKCBhbHQgLCBsZW5ndGgoYWx0X2dlbmVzLmxpc3RbWyBhbHQgXV0pKSwKICAgICAgICB2YWx1ZXMgPSBhcy5udW1lcmljKGFsdF9nZW5lcy5saXN0W1sgYWx0IF1dKQogICAgICAgICkgKQp9CgppZiAoIG5yb3coc3VuYnVyc3QuZGYpID4gMCApIHsKICBzdW5idXJzdF9wbG90W1syXV0gPC0gcGxvdF9seShzdW5idXJzdC5kZiwgaWRzID0gfmlkcywgbGFiZWxzID0gfmxhYmVscywgcGFyZW50cyA9IH5wYXJlbnRzLCB2YWx1ZXMgPSB+dmFsdWVzLCB0eXBlID0gJ3N1bmJ1cnN0Jywgd2lkdGggPSA2MDAsIGhlaWdodCA9IDYwMCkKfSBlbHNlIHsKICBzdW5idXJzdF9wbG90W1syXV0gPC0gTkEKfQoKIyMjIyMgQ3JlYXRlIGRpcmVjdG9yeSBmb3IgdGhlIHBsb3RzCnN1bW1hcnlQbG90c0RpciA8LSBwYXN0ZShyZXN1bHRzX2RpciwgInN1bW1hcnlQbG90cyIsIHNlcCA9ICIvIikKaWYgKCAhZmlsZS5leGlzdHMoc3VtbWFyeVBsb3RzRGlyKSApIHsKICBkaXIuY3JlYXRlKHN1bW1hcnlQbG90c0RpciwgcmVjdXJzaXZlPVRSVUUpCn0KICAKIyMjIyMgU2F2ZSBpbnRlcmFjdGl2ZSBwbG90IGFzIGh0bWwgZmlsZQpzYXZlV2lkZ2V0Rml4KHN1bmJ1cnN0X3Bsb3RbWzFdXSwgZmlsZSA9IHBhc3RlKHN1bW1hcnlQbG90c0RpciwgInN1bmJ1cnN0X3Bsb3RfYWxsLmh0bWwiLCBzZXAgPSAiLyIpKQoKaWYgKCAhaXMubmEoc3VuYnVyc3RfcGxvdFtbMl1dKSApIHsKICBzYXZlV2lkZ2V0Rml4KHN1bmJ1cnN0X3Bsb3RbWzJdXSwgZmlsZSA9IHBhc3RlKHN1bW1hcnlQbG90c0RpciwgInN1bmJ1cnN0X3Bsb3QuaHRtbCIsIHNlcCA9ICIvIikpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0oc3VuYnVyc3QuYWxsLmRmLCBzdW5idXJzdC5kZikKYGBgCgpgYGB7ciBnb2lfc3VtbWFyeV90YWJsZSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBQcmVwYXJlIGRhdGFmcmFtZSBmb3IgYSB0YWJsZSBzdW1tYXJpc2luZyBhbGwgYWx0ZXJlZCBnZW5lcwojIyMjIyBDcmVhdGUgbGlzdHMgd2l0aCBhbHRlcmF0aW9ucyBkZXRlY3RlZCBpbiBlYWNoIGdlbmUKZ2VuZXMubGlzdCA8LSBuYW1lcyhhbHRfZ2VuZXMuYWxsKQpzdW1tYXJ5X3RhYmxlLmxpc3QgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoKGdlbmVzLmxpc3QpKQpuYW1lcyhzdW1tYXJ5X3RhYmxlLmxpc3QpIDwtIGdlbmVzLmxpc3QKICAKIyMjIyMgR28gdGhyb3VnaCBhbGwgYWx0ZXJhdGVkIGdlbmVzIGFuZCBub3RlIHRoZSBhbHRlcmF0aW9ucyB0eXBlcwpmb3IgKCBnZW5lIGluIG5hbWVzKGFsdF9nZW5lcy5hbGwpICkgewogIGZvciAoIGFsdCBpbiBuYW1lcyhhbHRfZ2VuZXMuYWxsLmxpc3QpICkgewogICAgaWYgKCBnZW5lICVpbiUgbmFtZXMoYWx0X2dlbmVzLmFsbC5saXN0W1sgYWx0IF1dKSAgKSB7CiAgICAgIHN1bW1hcnlfdGFibGUubGlzdFtbIGdlbmUgXV0gPC0gYyggc3VtbWFyeV90YWJsZS5saXN0W1sgZ2VuZSBdXSwgIlllcyIgKQogICAgfSBlbHNlIHsKICAgICAgc3VtbWFyeV90YWJsZS5saXN0W1sgZ2VuZSBdXSA8LSBjKCBzdW1tYXJ5X3RhYmxlLmxpc3RbWyBnZW5lIF1dLCAiLSIgKQogICAgfQogIH0KICAKICAjIyMjIyBBZGQgbGlua3MgdG8gZXh0ZXJuYWwgcmVzb3VyY2VzCiAgIyMjIyMgUHJvdmlkZSBsaW5rIHRvIFZJQ0MgbWV0YS1rbm93bGVkZ2ViYXNlICggaHR0cHM6Ly9zZWFyY2guY2FuY2VydmFyaWFudHMub3JnICkKICBzdW1tYXJ5X3RhYmxlLmxpc3RbWyBnZW5lIF1dIDwtIGMoIHN1bW1hcnlfdGFibGUubGlzdFtbIGdlbmUgXV0sIHBhc3RlMCgiPGEgaHJlZj0naHR0cHM6Ly9zZWFyY2guY2FuY2VydmFyaWFudHMub3JnLyMiLCBnZW5lLCAiJyB0YXJnZXQ9J19ibGFuayc+VklDQzwvYT4iKSkKICAgICAgCiAgIyMjIyMgUHJvdmlkZSBsaW5rIHRvIE9uY29LQgogIGlmICggZ2VuZSAlaW4lIHJvd25hbWVzKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dKSApIHsKICAgIGlmICggcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV1bICBnZW5lLCAiT25jb0tCIl0gPT0gIlllcyIgKSB7CiAgICAgICAgICAKICAgICAgc3VtbWFyeV90YWJsZS5saXN0W1sgZ2VuZSBdXVsgbGVuZ3RoKHN1bW1hcnlfdGFibGUubGlzdFtbIGdlbmUgXV0pXSA8LSBwYXN0ZSggc3VtbWFyeV90YWJsZS5saXN0W1sgZ2VuZSBdXVsgbGVuZ3RoKHN1bW1hcnlfdGFibGUubGlzdFtbIGdlbmUgXV0pXSAsIHBhc3RlMCgiPGEgaHJlZj0naHR0cDovL29uY29rYi5vcmcvIy9nZW5lLyIsIGdlbmUsICInIHRhcmdldD0nX2JsYW5rJz5PbmNvS0I8L2E+IiksIHNlcCA9ICIsICIpCiAgICB9CiAgfQogICAgICAKICAjIyMjIyBQcm92aWRlIGxpbmsgdG8gQ0lWaUMgZGF0YWJhc2UgZHJ1Z2dhYmxlIGdlbmVzICggaHR0cHM6Ly9jaXZpY2RiLm9yZyApCiAgaWYgKCBnZW5lICVpbiUgY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX2NsaW5fZXZpZCJdXSRnZW5lICkgewogICAgc3VtbWFyeV90YWJsZS5saXN0W1sgZ2VuZSBdXVsgbGVuZ3RoKHN1bW1hcnlfdGFibGUubGlzdFtbIGdlbmUgXV0pXSA8LSBwYXN0ZSggc3VtbWFyeV90YWJsZS5saXN0W1sgZ2VuZSBdXVsgbGVuZ3RoKHN1bW1hcnlfdGFibGUubGlzdFtbIGdlbmUgXV0pXSAsIHBhc3RlMCgiPGEgaHJlZj0nIiwgdW5pcXVlKGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV1bIGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV0kZ2VuZSA9PSBnZW5lICwgImdlbmVfY2l2aWNfdXJsIl0pLCAiJyB0YXJnZXQ9J19ibGFuayc+Q0lWaUM8L2E+IiksIHNlcCA9ICIsICIpCiAgfQp9CgojIyMjIyBDb252ZXJ0IHRoZSBsaXN0IGludG8gZGF0YSBmcmFtZQpzdW1tYXJ5X3RhYmxlLmRmIDwtIGRhdGEuZnJhbWUobWF0cml4KHVubGlzdChzdW1tYXJ5X3RhYmxlLmxpc3QpLCBucm93PWxlbmd0aChzdW1tYXJ5X3RhYmxlLmxpc3QpLCBieXJvdz1UKSxzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQoKIyMjIyMgQWRkIGdlbmUgbmFtZXMgYW5kIG51bWJlciBvZiBzZWN0aW9uIGluIHdoaWNoIGluZGl2aWR1YWwgZ2VuZXMgYXJlIHJlcG9ydGVkCnN1bW1hcnlfdGFibGUuZGYgPC0gY2JpbmQobmFtZXMoc3VtbWFyeV90YWJsZS5saXN0KSwgc3VtbWFyeV90YWJsZS5kZikKc3VtbWFyeV90YWJsZS5kZiA8LSBjYmluZChzdW1tYXJ5X3RhYmxlLmRmLCBhcy5udW1lcmljKGFsdF9nZW5lcy5hbGwpKQpjb2xuYW1lcyhzdW1tYXJ5X3RhYmxlLmRmKSA8LSBjKCJHZW5lIiwgbmFtZXMoYWx0X2dlbmVzLmFsbC5saXN0KSwgIlJlc291cmNlcyIsICJDb3VudCIpCgojIyMjIyBBZGQgR2VuZUNhcmRzIGxpbmtzCnN1bW1hcnlfdGFibGUuZGYkR2VuZSA8LSBwYXN0ZTAoIjxhIGhyZWY9J2h0dHBzOi8vd3d3LmdlbmVjYXJkcy5vcmcvY2dpLWJpbi9jYXJkZGlzcC5wbD9nZW5lPSIsIHN1bW1hcnlfdGFibGUuZGYkR2VuZSwgIicgdGFyZ2V0PSdfYmxhbmsnPiIsIHN1bW1hcnlfdGFibGUuZGYkR2VuZSwgIjwvYT4iKQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CnJtKHN1bW1hcnlfdGFibGUubGlzdCkKYGBgCgoqKioKCjxkZXRhaWxzPgo8c3VtbWFyeT5JbnB1dCBkYXRhIHN1bW1hcnk8L3N1bW1hcnk+CgoqKlJlZmVyZW5jZSBwYXRpZW50IGNvaG9ydHMqKgoKVGhlIGZvbGxvd2luZyByZWZlcmVuY2UgcGF0aWVudCBjb2hvcnRzIHdlcmUgdXNlZCBmb3IgdGhlIGFuYWx5c2lzOgoKKiAqKmByIHBhc3RlKHJlZl9leHRfY2FuY2VyLCBleHRfY2FuY2VyX2dyb3VwLCBzZXA9IiAiKWAqKiBzYW1wbGVzIGZyb20gW1RoZSBDYW5jZXIgR2Vub21lIEF0bGFzXShodHRwczovL2dpdGh1Yi5jb20vdW1jY3IvUk5Bc2VxLUFuYWx5c2lzLVJlcG9ydC9ibG9iL21hc3Rlci9UQ0dBX3Byb2plY3RzX3N1bW1hcnkubWQjdGNnYS1wcm9qZWN0cy1zdW1tYXJ5KXt0YXJnZXQ9Il9ibGFuayJ9IHByb2plY3QgKFtyZWxhdGVkIHB1YmxpY2F0aW9uXShodHRwczovL3d3dy5uYXR1cmUuY29tL2FydGljbGVzL25nLjI3NjQpe3RhcmdldD0iX2JsYW5rIn0pCiogKipgciBwYXN0ZShyZWZfaW50X2NhbmNlciwgaW50X2NhbmNlcl9ncm91cCwgc2VwPSIgIilgKiogc2FtcGxlcyBmcm9tIFtVbml2ZXJzaXR5IG9mIE1lbGJvdXJuZSBDZW50cmUgZm9yIENhbmNlciBSZXNlYXJjaF0oaHR0cHM6Ly9yZXNlYXJjaC51bmltZWxiLmVkdS5hdS9jZW50cmUtZm9yLWNhbmNlci1yZXNlYXJjaC9ob21lKXt0YXJnZXQ9Il9ibGFuayJ9IHNhbXBsZXMgY29sbGVjdGlvbgoKKipJbnB1dCBnZW5lcyoqCgpPdXQgb2YgdGhlIGByIG5yb3cocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSlgIGlucHV0IGdlbmVzICoqYHIgbnJvdyhkYXRhLmFubm90KWAqKiBhcmUgdXNlZCBmb3IgYW5hbHlzZXM6CgoqICoqYHIgaWYgKHBhcmFtcyRmaWx0ZXIpIHsgbnJvdyhkYXRhLmFubm90KSAtIG5yb3coZ2VuZXMya2VlcFsgIWdlbmVzMmtlZXAkRVhQLCBdKSB9IGVsc2UgeyAoIjAiKSB9YCoqIGhhdmUgcmVsaWFibHkgZGV0ZWN0ZWQgZXhwcmVzc2lvbgoqICoqYHIgaWYgKHBhcmFtcyRmaWx0ZXIpIHsgbnJvdyhnZW5lczJrZWVwWyAhZ2VuZXMya2VlcCRFWFAsIF0pIH0gZWxzZSB7ICgiMCIpIH1gKiogYXJlIG5vdCBleHByZXNzZWQgYnV0IGFyZSBvZiBpbnRlcmVzdCBhbmQgYXJlIGluY2x1ZGVkIGluIGFuYWx5c2VzCiogYHIgaWYgKHBhcmFtcyRmaWx0ZXIpIHsgbnJvdyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dKSAtIG5yb3cocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pIH0gZWxzZSB7ICgiMCIpIH1gIGFyZSBlaXRoZXIgbm90IGV4cHJlc3NlZCBvciB0aGVpciBleHByZXNzaW9uIGxldmVsIGlzIHRvbyBsb3cgdG8gYmUgZGV0ZWN0ZWQKKiBgciBpZiAocGFyYW1zJGZpbHRlcikgeyBucm93KHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dKSAtIG5yb3coZGF0YS5hbm5vdCkgfSBlbHNlIHsgbnJvdyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dKSAtIG5yb3coZGF0YS5hbm5vdCkgfWAgZ2VuZXMgd2VyZSBpZ25vcmVkIGR1ZSB0byBsYWNrIG9mIFtIR05DXShodHRwczovL3d3dy5nZW5lbmFtZXMub3JnLyl7dGFyZ2V0PSJfYmxhbmsifS1hcHByb3ZlZCBnZW5lIHN5bWJvbAoKTk9URSwgdGhlIGByIGlmIChwYXJhbXMkZmlsdGVyKSB7IG5yb3cocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSkgLSBucm93KHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dKSArIG5yb3coZ2VuZXMya2VlcFsgIWdlbmVzMmtlZXAkRVhQLCBdKSB9IGVsc2UgeyAoIjAiKSB9YCBnZW5lcyB3aXRoIG5vL2xvdyBleHByZXNzaW9uIGFyZSBpbmRpY2F0ZWQgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluICpleHByZXNzaW9uIHN1bW1hcnkgdGFibGVzKiBpbiBbTXV0YXRlZCBnZW5lc10sIFtTdHJ1Y3R1cmFsIHZhcmlhbnRzXSwgW0NOIGFsdGVyZWQgZ2VuZXNdLCBbSW1tdW5lIG1hcmtlcnNdLCBbSFJEIGdlbmVzXSBhbmQgW0NhbmNlciBnZW5lc10gc2VjdGlvbnMuCgoKKipMaWJyYXJ5IHNpemUqKgoKQmFyLXBsb3QgaWxsdXN0cmF0aW5nIGxpYnJhcnkgc2l6ZSBmb3IgZWFjaCBzYW1wbGUuCgpgYGB7ciBsaWJyYXJ5X3NpemUsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDN9CmxpYnJhcnlfc2l6ZQpgYGAKCioqRGF0YSBmaWx0ZXJpbmcgYW5kIHRyYW5zZm9ybWF0aW9uKioKClRoZSByZWFkIGNvdW50IGRhdGEgd2VyZSBjb252ZXJ0ZWQgaW50byAqKmByIHBhcmFtcyR0cmFuc2Zvcm1gKipzIHVzaW5nICpbZWRnZVJdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9lZGdlUi5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9KiBmdW5jdGlvbnMuIGByIGlmICggIXBhcmFtcyRmaWx0ZXIgKSB7IGMoIlRoZSBvcHRpb24gZm9yIGZpbHRlcmluZyBvdXQgZ2VuZXMgd2l0aCBsb3cgY291bnRzIGlzIHN3aXRjaGVkIE9GRiIpIH0gZWxzZSBpZiAoIHBhcmFtcyRmaWx0ZXIgKSB7IGMoIkdlbmVzIHdpdGggbG93IGNvdW50cyB3ZXJlIGZpbHRlcmVkIG91dCIpIH1gLiBgciBpZiAoICFwYXJhbXMkbG9nICkgeyBjKCJUaGUgZGF0YSB3ZXJlIG5vdCBsb2ctdHJhbnNmb3JtZWQiKSB9IGVsc2UgaWYgKCBwYXJhbXMkbG9nICkgeyBjKCJUaGUgZGF0YSB3ZXJlIGxvZzItdHJhbnNmb3JtZWQiKSB9YC4gCgpgciBpZiAoIHBhcmFtcyR0cmFuc2Zvcm0gPT0gIkNQTSIgKSB7IHBhc3RlMCgiVGhlIENQTSBvZiAxIChjdXQtb2ZmIGZvciByZW1vdmluZyBsb3cgZXhwcmVzc2VkIGdlbmVzKSBjb3JyZXNwb25kcyB0byAiLCBjcG0ubWluLCAiIHJlYWRzIGluIHNhbXBsZSB3aXRoIHRoZSBsb3dlc3Qgc2VxdWVuY2luZyBkZXB0aCwgYW5kICIsIGNwbS5tYXgsICIgcmVhZHMgaW4gc2FtcGxlIHdpdGggdGhlIGdyZWF0ZXN0IHNlcXVlbmNpbmcgZGVwdGguIFRoZSBwbG90IGJlbG93IHByZXNlbnRzIHRoZSByZWxhdGlvbiBiZXR3ZWVuIHJlYWQgY291bnRzIGFuZCB0aGUgY29ycmVzcG9uZGluZyAiLCBwYXJhbXMkdHJhbnNmb3JtLCAiIHZhbHVlcyBpbiB0aGUgcGF0aWVudCBkYXRhLiBUaGUgcmVkIHZlcnRpY2FsIGxpbmUgaW5kaWNhdGVzIHRoZSB0aHJlc2hvbGQgZm9yIGZpbHRlcmluZyBnZW5lcyB3aXRoIGxvdyBjb3VudHMuIikgfSBlbHNlIHsgcGFzdGUwKCJUaGUgcGxvdCBiZWxvdyBwcmVzZW50cyB0aGUgcmVsYXRpb24gYmV0d2VlbiByZWFkIGNvdW50cyBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgIiwgcGFyYW1zJHRyYW5zZm9ybSwgIiB2YWx1ZXMgaW4gdGhlIHBhdGllbnQgZGF0YS4gVGhlIHJlZCB2ZXJ0aWNhbCBsaW5lIGluZGljYXRlcyB0aGUgdGhyZXNob2xkIGZvciBmaWx0ZXJpbmcgZ2VuZXMgd2l0aCBsb3cgY291bnRzLiIpIH1gCgpgYGB7ciBjb3VudHNfdnNfdHJhbnNmb3JtZWQsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIGV2YWwgPSBwYXJhbXMkZmlsdGVyfQpjb3VudHNfdnNfdHJhbnNmb3JtZWQKYGBgCgoKUGxvdChzKSBiZWxvdyBwcmVzZW50IGByIHBhcmFtcyR0cmFuc2Zvcm1gIGRhdGEgZGlzdHJpYnV0aW9uIGByIGlmICggcGFyYW1zJGZpbHRlciApIHsgYygiIGJlZm9yZSBhbmQgYWZ0ZXIgZmlsdGVyaW5nIGdlbmVzIHdpdGggbG93IGNvdW50cy4iKSB9IGVsc2UgeyBjYXQoIi4iKX1gCiAKYGBge3IgZGF0YV90cmFuc2Zvcm1hdGlvbl9kaXNwbGF5LCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDUsIHRodW1iID0gbGlzdCh3aWR0aCA9IDE1LCBoZWlnaHQgPSAxNSkgfQpkYXRhX3RyYW5zZm9ybWF0aW9uX25vbmZpbHRlcmVkCgppZiAoIHBhcmFtcyRmaWx0ZXIgKSB7CiAgZGF0YV90cmFuc2Zvcm1hdGlvbl9maWx0ZXJlZAp9CmBgYAoKKipEYXRhIG5vcm1hbGlzYXRpb24qKgoKRHVyaW5nIHRoZSBzYW1wbGUgcHJlcGFyYXRpb24gb3Igc2VxdWVuY2luZyBwcm9jZXNzLCBleHRlcm5hbCBmYWN0b3JzIHRoYXQgYXJlIG5vdCBvZiBiaW9sb2dpY2FsIGludGVyZXN0IGNhbiBhZmZlY3QgdGhlIGV4cHJlc3Npb24gb2YgaW5kaXZpZHVhbCBzYW1wbGVzLiBJdCBpcyBhc3N1bWVkIHRoYXQgYWxsIHNhbXBsZXMgc2hvdWxkIGhhdmUgYSBzaW1pbGFyIHJhbmdlIGFuZCBkaXN0cmlidXRpb24gb2YgZXhwcmVzc2lvbiB2YWx1ZXMuIE5vcm1hbGlzYXRpb24gZm9yIHNhbXBsZS1zcGVjaWZpYyBlZmZlY3RzIGlzIHJlcXVpcmVkIHRvIGVuc3VyZSB0aGF0IHRoZSBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbnMgb2YgZWFjaCBzYW1wbGUgYXJlIHNpbWlsYXIgYWNyb3NzIHRoZSBlbnRpcmUgZXhwZXJpbWVudC4gTm9ybWFsaXNhdGlvbiBpcyBwZXJmb3JtZWQgdXNpbmcgKipgciBwYXJhbXMkbm9ybWAqKiBtZXRob2QuCgpCb3gtcGxvdHMgYmVsb3cgcHJlc2VudCBgciBwYXJhbXMkdHJhbnNmb3JtYCBkYXRhIGZvciBpbmRpdmlkdWFsIHNhbXBsZXMsIGNvbG91cmVkIGJ5IHNhbXBsZSBncm91cHMsIGJlZm9yZSBhbmQgYWZ0ZXIgYHIgcGFyYW1zJG5vcm1gIG5vcm1hbGlzYXRpb24uCgpgYGB7ciBkYXRhX25vcm1hbGlzYXRpb25fZGlzcGxheSwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA5LCB0aHVtYiA9IGxpc3Qod2lkdGggPSAxNSwgaGVpZ2h0ID0gMTUpIH0KZGF0YV9ub25ub3JtYWxpc2VkCgppZiAoIHBhcmFtcyRub3JtICE9ICJub25lIiApIHsKICBkYXRhX25vcm1hbGlzZWQKfQpgYGAKCioqRXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyoqCgpgciBpZiAoIHBhcmFtcyRiYXRjaF9ybSApIHsgcGFzdGUwKCJUaGUgZXhwcmVzc2lvbiBkYXRhIHByb2R1Y2VkIGJ5IGRpZmZlcmVudCBzdHVkaWVzIGFyZSBjb25mb3VuZGVkIGJ5IG5vbi1iaW9sb2dpY2FsIGV4cGVyaW1lbnRhbCB2YXJpYW5jZXMgdGhhdCBwcmV2ZW50IGRpcmVjdCBjb21wYXJpc29uIG9mIHNhbXBsZXMgZnJvbSBkaWZmZXJlbnQgc3R1ZGllcy4gSW4gb3JkZXIgdG8gbWluaW1pc2UgdGhlIHZhcmlhbmNlIGNhdXNlZCBieSBjb25mb3VuZGluZyBmYWN0b3JzIFtsaW1tYSByZW1vdmVCYXRjaEVmZmVjdF0oaHR0cDovL3dlYi5taXQuZWR1L35yL2N1cnJlbnQvYXJjaC9pMzg2X2xpbnV4MjYvbGliL1IvbGlicmFyeS9saW1tYS9odG1sL3JlbW92ZUJhdGNoRWZmZWN0Lmh0bWwpe3RhcmdldD1cIl9ibGFua1wifSBtZXRob2Qgd2FzIHVzZWQgdG8gYWRqdXN0IGV4cHJlc3Npb24gbWVhc3VyZW1lbnRzIGZvciBwb3RlbnRpYWwgYmF0Y2ggZWZmZWN0cy4gSW4gYnJpZWYsIHRoZSBzdHJhdGVneSBpcyB0byBjb25zaWRlciB0aGUgaW52ZXN0aWdhdGVkIHNhbXBsZSBhbmQgdGhlICIsIHBhc3RlKHJlZl9pbnRfY2FuY2VyLCBpbnRfY2FuY2VyX2dyb3VwLCBzZXA9IiAiKSwgIiBzYW1wbGVzIGFzIG9uZSBiYXRjaCAocmVnYXJkbGVzcyBvZiB0aGUgaW52ZXN0aWdhdGVkIHNhbXBsZSB0aXNzdWUgb3JpZ2luKSBhbmQgIiwgcGFzdGUocmVmX2V4dF9jYW5jZXIsIGV4dF9jYW5jZXJfZ3JvdXAsIHNlcD0iICIpLCAiIHNhbXBsZXMgKG9mIGFueSBjYW5jZXIgdHlwZSkgYXMgYW5vdGhlciBiYXRjaC4gVGhlIG9iamVjdGl2ZSBpcyB0byByZW1vdmUgYXMgbXVjaCBkYXRhIHZhcmlhdGlvbiBkdWUgdG8gdGVjaG5pY2FsIGZhY3RvcnMgYXMgcG9zc2libGUuIikgfWAKClByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgKFBDQSkgd2FzIHBlcmZvcm1lZCB0byByZWR1Y2UgdGhlIGRpbWVuc2lvbmFsaXR5IG9mIGRhdGEgdG8gdmlzdWFsbHkgYXNzZXNzIHNpbWlsYXJpdGllcyBhbmQgZGlmZmVyZW5jZXMgYmV0d2VlbiBzYW1wbGVzLiBUaGlzIGV4cGxvcmF0b3J5IGFuYWx5c2lzIGZhY2lsaXRhdGVzIGlkZW50aWZpY2F0aW9uIG9mIHRoZSBrZXkgZmFjdG9ycyBhZmZlY3RpbmcgdGhlIHZhcmlhYmlsaXR5IGluIHRoZSBleHByZXNzaW9uIGRhdGEuCgoqICoqUENBIHBsb3QqKgoKYHIgaWYgKCBwYXJhbXMkYmF0Y2hfcm0gKSB7IHBhc3RlMCgiU2NhdHRlci1wbG90cyBvZiB0aGUgZmlyc3QgMiBwcmluY2lwYWwgY29tcG9uZW50cyAoUENzKSBjb25zdGl0dXRpbmcgdGhlIHByaW1hcnkgc291cmNlIG9mIHZhcmlhdGlvbiBpbiB0aGUgZGF0YSBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdHMgY29ycmVjdGlvbi4iKSB9IGVsc2UgeyBwYXN0ZTAoIlNjYXR0ZXItcGxvdCBvZiB0aGUgZmlyc3QgMiBwcmluY2lwYWwgY29tcG9uZW50cyAoUENzKSBjb25zdGl0dXRpbmcgdGhlIHByaW1hcnkgc291cmNlIG9mIHZhcmlhdGlvbiBpbiB0aGUgZGF0YS4iKSB9YAoKYGBge3IgcGNhX2NvbWJpbmVkX2RhdGFfZGlzcGxheSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOCB9CnJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInBjYV9jb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXVtbMl1dCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKYGBge3IgcGNhX2JhdGNoX2VmZmVjdF9jb3JyZWN0ZWRfZGlzcGxheSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPXBhcmFtcyRiYXRjaF9ybSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDggfQpyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJwY2FfYmF0Y2hfZWZmZWN0X2NvcnJlY3RlZCJdXVtbMl1dCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKKiAqKlNjcmVlLXBsb3QqKgoKYHIgaWYgKCBwYXJhbXMkYmF0Y2hfcm0gKSB7IHBhc3RlMCgiU2NyZWUtcGxvdHMgcHJlc2VudGluZyB0aGUgZnJhY3Rpb24gb2YgdG90YWwgdmFyaWFuY2UgKCp5LWF4aXMqKSBhdHRyaWJ1dGVkIHRvIGVhY2ggUEMgKCp4LWF4aXMqKSBiZWZvcmUgYW5kIGFmdGVyIGJhdGNoIGVmZmVjdHMgY29ycmVjdGlvbi4gVGhlIFBDcyBhcmUgb3JkZXJlZCBieSBkZWNyZWFzaW5nIG9yZGVyIG9mIGNvbnRyaWJ1dGlvbiB0byB0b3RhbCB2YXJpYW5jZS4iKSB9IGVsc2UgeyBwYXN0ZTAoIlNjcmVlLXBsb3QgcHJlc2VudGluZyB0aGUgZnJhY3Rpb24gb2YgdG90YWwgdmFyaWFuY2UgKCp5LWF4aXMqKSBhdHRyaWJ1dGVkIHRvIGVhY2ggUEMgKCp4LWF4aXMqKS4gVGhlIFBDcyBhcmUgb3JkZXJlZCBieSBkZWNyZWFzaW5nIG9yZGVyIG9mIGNvbnRyaWJ1dGlvbiB0byB0b3RhbCB2YXJpYW5jZS4gIikgfWAKCmBgYHtyIHNjcmVlX2NvbWJpbmVkX2RhdGFfZGlzcGxheSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDUgfQpyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJwY2FfY29tYmluZWRfZGF0YV9wcm9jZXNzZWQiXV1bWzNdXQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQpgYGAKCmByIGlmICggcGFyYW1zJGJhdGNoX3JtICkgeyBjYXQoIiogQWZ0ZXIgYmF0Y2gtZWZmZWN0cyBjb3JyZWN0aW9uIikgfSBlbHNlIHsgY2F0KCIgIikgfWAKCmBgYHtyIHNjcmVlX2JhdGNoX2VmZmVjdF9jb3JyZWN0ZWRfZGlzcGxheSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPXBhcmFtcyRiYXRjaF9ybSwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQgPSA1IH0KcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicGNhX2JhdGNoX2VmZmVjdF9jb3JyZWN0ZWQiXV1bWzNdXQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQpgYGAKCiogKipSTEUgcGxvdCoqCgpUaGUgcmVsYXRpdmUgbG9nIGV4cHJlc3Npb24gKFJMRSkgcGxvdCBpcyBhIHVzZWZ1bCBkaWFnbm9zdGljIHRvb2wgdG8gdmlzdWFsaXNlIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBkaXN0cmlidXRpb25zIG9mIHJlYWQgY291bnRzIGFjcm9zcyBzYW1wbGVzLiBJdCBzaG93cyBib3hwbG90IG9mIHRoZSBsb2ctcmF0aW9zIG9mIHRoZSBnZW5lLWxldmVsIHJlYWQgY291bnRzICgqeS1heGlzKikgb2YgZWFjaCBzYW1wbGUgdG8gdGhvc2Ugb2YgYSByZWZlcmVuY2Ugc2FtcGxlIChkZWZpbmVkIGFzIHRoZSBtZWRpYW4gYWNyb3NzIHRoZSBzYW1wbGVzKS4gSWRlYWxseSwgdGhlIGRpc3RyaWJ1dGlvbnMgc2hvdWxkIGJlIGNlbnRlcmVkIGFyb3VuZCB0aGUgemVybyBsaW5lIGFuZCBhcyB0aWdodCBhcyBwb3NzaWJsZS4gQ2xlYXIgZGV2aWF0aW9ucyBpbmRpY2F0ZSB0aGUgbmVlZCBmb3Igbm9ybWFsaXNhdGlvbiBhbmQvb3IgdGhlIHByZXNlbmNlIG9mIG91dGx5aW5nIHNhbXBsZXMuCgpgYGB7ciBybGVfZGlzcGxheSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDksIHRodW1iID0gbGlzdCh3aWR0aCA9IDE1LCBoZWlnaHQgPSAxNSl9CnJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInJsZV9jb21iaW5lZF9kYXRhX3Byb2Nlc3NlZCJdXQoKaWYgKCBwYXJhbXMkbm9ybSAhPSAibm9uZSIgKSB7CiAgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sicmxlX2JhdGNoX2VmZmVjdF9jb3JyZWN0ZWQiXV0KfQpgYGAKCjwvZGV0YWlscz4KCmByIGlmICggcnVuQ2xpbmljYWxDaHVuayApIHsgYygiKioqIikgfWAKCmByIGlmICggcnVuQ2xpbmljYWxDaHVuayApIHsgYygiIyMgQ2xpbmljYWwgaW5mb3JtYXRpb24iKSB9YAoKYHIgaWYgKCBydW5DbGluaWNhbENodW5rICkgeyBjKCIjIyMjIFRyZWF0bWVudCB0aW1lbGluZSIpIH1gCgpgciBpZiAoIHJ1bkNsaW5pY2FsQ2h1bmsgKSB7IGMoIk5PVEU6IGZvciBjb25maWRlbnRpYWxpdHkgcmVhc29ucywgdGhlIHN0YXJ0IG9mIHRoZSB0aW1lbGluZSAoKngtYXhpcyopIHByb2plY3RpbmcgcGF0aWVudCdzIHRyZWF0bWVudCByZWdpbWVucyAoKnktYXhpcyopIGlzIHNldCB0byAxc3QgSmFudWFyeSAyMDAwLCBidXQgdGhlIHRyZWF0bWVudHMgbGVuZ3RocyBhcmUgcHJlc2VydmVkLiIpIH1gCgpgYGB7ciB0cmVhdG1lbnRfdGltZWxpbmVfcGxvdCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDUsIHRodW1iID0gbGlzdCh3aWR0aCA9IDE1LCBoZWlnaHQgPSAxNSksIGV2YWwgPSBydW5DbGluaWNhbENodW5rIH0KIyMjIyMgUHJlc2VudCB0aGUgdHJlYXRtZW50IHRpbWVsaW5lIHBsb3QKdHJlYXRtZW50X3RpbWVsaW5lCmBgYAoKKioqCgojIyBGaW5kaW5ncyBzdW1tYXJ5IHsudGFic2V0fQoKIyMjIFBlci1hbHRlcmF0aW9uIHBsb3QKCmByIGlmICggIWlzLm5hKHN1bmJ1cnN0X3Bsb3RbWzJdXSkgKSB7IGMoIioqR2VuZXMqKiBsaXN0ZWQgKippbiBhdCBsZWFzdCB0d28gc2VjdGlvbnMqKiBvZiB0aGlzIHJlcG9ydCIpIH0gZWxzZSB7IGMoIioqQWxsIGFsdGVyZWQgZ2VuZXMqKiIpICB9YCBhcmUgc3VtbWFyaXNlZCBpbiB0aGUgcGxvdCBiZWxvdy4gYHIgaWYgKCAhaXMubmEoc3VuYnVyc3RfcGxvdFtbMl1dKSApIHsgYygiVGhlc2UgZ2VuZXMgbWF5IGJlIG9mIHBhcnRpY3VsYXIgaW50ZXJlc3QgZ2l2ZW4gdGhhdCB0aGUgZXZpZGVuY2UgZm9yIHRoZWlyIGFsdGVyYXRpb24gaXMgZGVyaXZlZCBmcm9tIG11bHRpcGxlIHNvdXJjZXMuIikgIH1gIFRoZSBudW1iZXIgbmV4dCB0byBlYWNoIGdlbmUgaW5kaWNhdGVzIHRoZSBudW1iZXIgb2YgdGltZXMgaXQgYXBwZWFycyBhY3Jvc3MgdGhlIGZvbGxvd2luZyByZXBvcnQgc2VjdGlvbnM6IGByIGlmICggcnVuUGNnckNodW5rICkgeyBjKCJbTXV0YXRlZCBnZW5lc10sICIpIH1gIGByIGlmICggcnVuRnVzaW9uQ2h1bmsgKSB7IGMoIltGdXNpb24gZ2VuZXNdIChzdXBwb3J0ZWQgYnkgZ2Vub21pYyBkYXRhIG9yIHJlcG9ydGVkIGluIEZ1c2lvbkdEQiksICIpIH1gIGByIGlmICggcnVuU1ZzQ2h1bmsgKSB7IGMoIltTdHJ1Y3R1cmFsIHZhcmlhbnRzXSwgIikgfWAgYHIgaWYgKCBydW5QdXJwbGVDaHVuayApIHsgcGFzdGUwKCJbQ04gYWx0ZXJlZCBnZW5lc10gKENOIHZhbHVlcyA9PCAiLCBjbl9ib3R0b20sICIgb3IgPj0gIiwgY25fdG9wLCAiIGFuZCByZXBvcnRlZCBhcyBjYW5jZXIgZ2VuZXMpIikgfWAgW0ltbXVuZSBtYXJrZXJzXSBvciBbSFJEIGdlbmVzXS4gVGhhdCBudW1iZXIgaXMgYWxzbyByZWZsZWN0ZWQgYnkgdGhlICp3aWR0aCogb2YgY29ycmVzcG9uZGluZyBicmFuY2hlcy4gQ2xpY2sgb24gdGhlIGNhdGVnb3J5IG9mIGludGVyZXN0IHRvIGV4cGFuZCBjb3JyZXNwb25kaW5nIGJyYW5jaGVzLiBHZW5lcyB3aXRoaW4gZWFjaCBjYXRlZ29yeSBhcmUgb3JkZXJlZCBieSB0aGUgbnVtYmVyIG9mIHJlcG9ydCBzZWN0aW9ucyBpbiB3aGljaCB0aGV5IGFwcGVhciBhbmQgdGhlbiBhbHBoYWJldGljYWxseS4KCmByIGlmICggaXMubmEoc3VuYnVyc3RfcGxvdFtbMl1dKSApIHsgYygiPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+Tk9URTwvc3Bhbj4sIG5vIGdlbmVzIGFyZSBsaXN0ZWQgaW4gbW9yZSB0aGVuIG9uZSBzZWN0aW9uLiIpIH1gCiAgCmBgYHtyIGZpbmRpbmdzX3N1bW1hcnlfcGxvdCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA2IH0KIyMjIyMgVXBkYXRlIE15U1FMIGNvbW1lbmQgdG8gcG9wdWxhdGUgUk5BLXNlcSBkYXRhIHBvcnRhbApteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGUsICJGaW5kaW5ncyBzdW1tYXJ5IikKbXlzcWxfcG9wdWxhdGVfdXBkYXRlIDwtIHBhc3RlMChteXNxbF9wb3B1bGF0ZV91cGRhdGUsICJGaW5kaW5ncyBzdW1tYXJ5IikKCiMjIyMjIFByZXNlbnQgcGVyLWFsdGVyYXRpb24gZmluZGluZ3Mgc3VtbWFyeSBzdW5idXJzdCBwbG90IGZvciBhbGwgYWx0ZXJlZCBnZW5lcwppZiAoICFpcy5uYShzdW5idXJzdF9wbG90W1syXV0pICkgewogIHN1bmJ1cnN0X3Bsb3RbWzJdXQp9IGVsc2UgewogIHN1bmJ1cnN0X3Bsb3RbWzFdXQp9CgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKYHIgaWYgKCAhaXMubmEoc3VuYnVyc3RfcGxvdFtbMl1dKSApIHsgYygiPGRldGFpbHM+XG48c3VtbWFyeT5TaG93IGFsbCBhbHRlcmVkIGdlbmVzPC9zdW1tYXJ5PiIpIH1gCgpgYGB7ciBmaW5kaW5nc19zdW1tYXJ5X2FsbF9wbG90LCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA2IH0KIyMjIyMgUHJlc2VudCBwZXItYWx0ZXJhdGlvbiBmaW5kaW5ncyBzdW1tYXJ5IHN1bmJ1cnN0IHBsb3QgZm9yIGFsdGVyZWQgZ2VuZXMgbGlzdGVkIGluIGF0IGxlYXN0IHR3byByZXBvcnQgc2VjdGlvbnMKaWYgKCAhaXMubmEoc3VuYnVyc3RfcGxvdFtbMl1dKSApIHsKICBzdW5idXJzdF9wbG90W1sxXV0KfQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQpgYGAKCmByIGlmICggIWlzLm5hKHN1bmJ1cnN0X3Bsb3RbWzJdXSkgKSB7IGMoIjwvZGV0YWlscz4iKSB9YAoKYGBge3IgY2xlYXJfc3BhY2UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSB9CnJtKGNvdW50c192c190cmFuc2Zvcm1lZCwgZGF0YV90cmFuc2Zvcm1hdGlvbl9ub25maWx0ZXJlZCwgZGF0YV90cmFuc2Zvcm1hdGlvbl9maWx0ZXJlZCwgZGF0YV9ub25ub3JtYWxpc2VkLCBkYXRhX25vcm1hbGlzZWQsIHRyZWF0bWVudF90aW1lbGluZSwgZGF0YS5hbm5vdCkKYGBgCgoqKioKCiMjIyBQZXItZ2VuZSB0YWJsZQoKVGFibGUgc3VtbWFyaXNpbmcgKiphbGwgYWx0ZXJlZCBnZW5lcyoqIGxpc3RlZCBhY3Jvc3MgZm9sbG93aW5nIHJlcG9ydCBzZWN0aW9uczogYHIgaWYgKCBydW5QY2dyQ2h1bmsgKSB7IGMoIltNdXRhdGVkIGdlbmVzXSwgIikgfWAgYHIgaWYgKCBydW5GdXNpb25DaHVuayApIHsgYygiW0Z1c2lvbiBnZW5lc10gKHN1cHBvcnRlZCBieSBnZW5vbWljIGRhdGEgb3IgcmVwb3J0ZWQgaW4gRnVzaW9uR0RCKSwgIikgfWAgYHIgaWYgKCBydW5TVnNDaHVuayApIHsgYygiW1N0cnVjdHVyYWwgdmFyaWFudHNdLCAiKSB9YCBgciBpZiAoIHJ1blB1cnBsZUNodW5rICkgeyBwYXN0ZTAoIltDTiBhbHRlcmVkIGdlbmVzXSAoQ04gdmFsdWVzID08ICIsIGNuX2JvdHRvbSwgIiBvciA+PSAiLCBjbl90b3AsICIgYW5kIHJlcG9ydGVkIGFzIGNhbmNlciBnZW5lcykiKSB9YCBbSW1tdW5lIG1hcmtlcnNdIG9yIFtIUkQgZ2VuZXNdLiBUaGUgKlJlc291cmNlcyogY29sdW1uIGNvbnRhaW5zIGxpbmtzIHRvIGRhdGFiYXNlcyB0aGF0IG1heSBwcm92aWRlIGFkZGl0aW9uYWwgc291cmNlIG9mIGV2aWRlbmNlIGZvciB0aGUgYWx0ZXJlZCBnZW5lcycgY2xpbmljYWwgc2lnbmlmaWNhbmNlLiBHZW5lcyBvcmRlcmVkIGJ5IHRoZSBudW1iZXIgb2YgcmVwb3J0IHNlY3Rpb25zIHRoZXkgYXBwZWFyIGluICgqQ291bnQqIGNvbHVtbikgYW5kIHRoZW4gYWxwaGFiZXRpY2FsbHkuCgpgciBpZiAoIGlzLm5hKHN1bmJ1cnN0X3Bsb3RbWzJdXSkgKSB7IGMoIjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCBubyBnZW5lcyBhcmUgbGlzdGVkIGluIG1vcmUgdGhlbiBvbmUgc2VjdGlvbi4iKSB9YAoKYGBge3IgZmluZGluZ3Nfc3VtbWFyeV90YWJsZSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFIH0KIyMjIyMgUHJlc2VudCBwZXItZ2VuZSBmaW5kaW5ncyBzdW1tYXJ5IHRhYmxlCmZpbmRpbmdzLnN1bW1hcnkgPC0gRFQ6OmRhdGF0YWJsZSggZGF0YSA9IHN1bW1hcnlfdGFibGUuZGYsIGZpbHRlcj0ibm9uZSIsIHJvd25hbWVzID0gRkFMU0UsIGV4dGVuc2lvbnMgPSBjKCdCdXR0b25zJywnU2Nyb2xsZXInKSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEwLCBkb20gPSAnQmZybHRpcCcsIGJ1dHRvbnMgPSBjKCdleGNlbCcsICdjc3YnLCAncGRmJywnY29weScsJ2NvbHZpcycpLCBzY3JvbGxYID0gVFJVRSwgZGVmZXJSZW5kZXIgPSBUUlVFLCBzY3JvbGxZID0gIjMzM3B4Iiwgc2Nyb2xsZXIgPSBUUlVFKSwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQ1NSwgY2FwdGlvbiA9IGh0bWx0b29sczo6dGFncyRjYXB0aW9uKCBzdHlsZSA9ICdjYXB0aW9uLXNpZGU6IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgY29sb3I6Z3JleTsgZm9udC1zaXplOjEwMCUnKSwgZXNjYXBlID0gRkFMU0UpICU+JQogICAgICBEVDo6Zm9ybWF0U3R5bGUoIGNvbHVtbnMgPSBjb2xuYW1lcyhzdW1tYXJ5X3RhYmxlLmRmKSwgYGZvbnQtc2l6ZWAgPSAnMTJweCcsICd0ZXh0LWFsaWduJyA9ICdjZW50ZXInICkgJT4lCiAgICAgICMjIyMjIENvbG91ciBjZWxscyBhY2NvcmRpbmcgdG8gZXZpZGVuY2UgbGV2ZWwgYW5kIHRydXN0IHJhdGluZwogICAgICBEVDo6Zm9ybWF0U3R5bGUoY29sdW1ucyA9IGNvbG5hbWVzKHN1bW1hcnlfdGFibGUuZGYpW2MoMjpuY29sKHN1bW1hcnlfdGFibGUuZGYpLTIpXSwgCiAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kQ29sb3IgPSBEVDo6c3R5bGVFcXVhbChjKCItIiwgIlllcyIpLCBjKCJ0cmFuc3BhcmVudCIsICJibGFjayIpKSwgY29sb3IgPSBEVDo6c3R5bGVFcXVhbChjKCItIiwgIlllcyIpLCBjKCJibGFjayIsICJ3aGl0ZSIpKSkKCmZpbmRpbmdzLnN1bW1hcnkKCiMjIyMjIENyZWF0ZSBkaXJlY3RvcnkgZm9yIHRhYmxlcwpzdW1tYXJ5VGFibGVEaXIgPC0gcGFzdGUocmVzdWx0c19kaXIsICJzdW1tYXJ5VGFibGVzIiwgc2VwID0gIi8iKQppZiAoICFmaWxlLmV4aXN0cyhzdW1tYXJ5VGFibGVEaXIpICkgewogICAgZGlyLmNyZWF0ZShzdW1tYXJ5VGFibGVEaXIsIHJlY3Vyc2l2ZT1UUlVFKQp9CgpzYXZlV2lkZ2V0Rml4KHdpZGdldD1maW5kaW5ncy5zdW1tYXJ5LCBmaWxlPXBhc3RlKHN1bW1hcnlUYWJsZURpciwgImZpbmRpbmdzLnN1bW1hcnkuaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShzdW1tYXJ5X3RhYmxlLmRmLCBmaW5kaW5ncy5zdW1tYXJ5LCBzdW5idXJzdF9wbG90KQpgYGAKCioqKgoKIyMgTXV0YXRlZCBnZW5lcwoKbVJOQSBleHByZXNzaW9uIGxldmVscyBvZiBnZW5lcyBjb250YWluaW5nIHNpbmdsZSBudWNsZW90aWRlIHZhcmlhbnRzIChTTlZzKSBvciBpbnNlcnRpb25zL2RlbGV0aW9ucyAoaW5kZWxzKSwgb2J0YWluZWQgZnJvbSB0aGUgW1BDR1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaWd2ZW4vcGNncil7dGFyZ2V0PSJfYmxhbmsifSByZXBvcnQsIGluIHBhdGllbnQncyBzYW1wbGUgYW5kIHRoZWlyIGF2ZXJhZ2UgbVJOQSBleHByZXNzaW9uIGluIHNhbXBsZXMgZnJvbSBjYW5jZXIgY29ob3J0cy4gTk9URSwgb25seSBQQ0dSIFt0aWVyXShodHRwczovL3BjZ3IucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L3RpZXJfc3lzdGVtcy5odG1sI3RpZXItbW9kZWwtMi1wY2dyLWFjbWcpe3RhcmdldD0iX2JsYW5rIn0gMS1gciBwYXJhbXMkcGNncl90aWVyYCBgciBpZiAoIHBhcmFtcyRwY2dyX3NwbGljZV92YXJzICkgeyBjKCJhbmQgbm9uLWNvZGluZyBzcGxpY2UgcmVnaW9uICIgKSB9YCB2YXJpYW50cyBhcmUgcmVwb3J0ZWQuCgpgciBpZiAoICFydW5QY2dyQ2h1bmsgKSB7IGMoIk11dGF0aW9uIGRhdGEgZm9yIHRoaXMgc2FtcGxlIGlzICoqTk9UIEFWQUlMQUJMRSoqLiIpIH1gIAoKIyMjIC0gU3VtbWFyeSB0YWJsZSB7LnRhYnNldH0KCk91dCBvZiB0aGUgYHIgbGVuZ3RoKHVuaXF1ZShyZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV0kU1lNQk9MKSlgIG11dGF0ZWQgZ2VuZXMgYHIgbGVuZ3RoKHVuaXF1ZShyZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV1bIHJlZl9nZW5lcy5saXN0W1sicGNnciJdXSRUSUVSICVpbiUgYygxOnBhcmFtcyRwY2dyX3RpZXIpLCBdJFNZTUJPTCkpYCBpbmNsdWRlIFt0aWVyXShodHRwczovL3BjZ3IucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L3RpZXJfc3lzdGVtcy5odG1sI3RpZXItbW9kZWwtMi1wY2dyLWFjbWcpe3RhcmdldD0iX2JsYW5rIn0gMS1gciBwYXJhbXMkcGNncl90aWVyYCB2YXJpYW50cyBgciBpZiAoIHBhcmFtcyRwY2dyX3NwbGljZV92YXJzICkgeyBwYXN0ZTAoImFuZCAiLCBsZW5ndGgocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJE11dGF0ZWQpIC0gbGVuZ3RoKHVuaXF1ZShyZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV1bIHJlZl9nZW5lcy5saXN0W1sicGNnciJdXSRUSUVSICVpbiUgYygxOnBhcmFtcyRwY2dyX3RpZXIpLCBdJFNZTUJPTCkpLCAiIG5vbi1jb2Rpbmcgc3BsaWNlIHJlZ2lvbiB2YXJpYW50IiApIH1gLiBPZiB0aGVzZSwgdGhlIGV4cHJlc3Npb24gb2YgKipgciBsZW5ndGgod2hpY2gocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJE11dGF0ZWQgJWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKWAqKiB3YXMgcmVsaWFibHkgbWVhc3VyZWQgaW4gcGF0aWVudCdzIHNhbXBsZS4gVGhlIHJlbWFpbmluZyBgciBsZW5ndGgod2hpY2gocmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJE11dGF0ZWQgJSFpbiUgcm93bmFtZXMocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pKSlgIGdlbmVzIGFyZSBlaXRoZXIgbm90IGV4cHJlc3NlZCBvciB0aGVpciBleHByZXNzaW9uIGxldmVsIGlzIHRvbyBsb3cgdG8gYmUgZGV0ZWN0ZWQgKGluZGljYXRlZCBpbiA8c3BhbiBzdHlsZT0iY29sb3I6IzgwODA4MCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMpLgoKIyMjIyBQZXJjZW50aWxlcwoKYGBge3IgbXV0X2dlbmVzX3RhYmxlX3BlcmMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blBjZ3JDaHVua30KIyMjIyMgVXBkYXRlIE15U1FMIGNvbW1lbmQgdG8gcG9wdWxhdGUgUk5BLXNlcSBkYXRhIHBvcnRhbApteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGUsICIsTXV0YXRlZCBnZW5lcyIpCm15c3FsX3BvcHVsYXRlX3VwZGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGVfdXBkYXRlLCAiLE11dGF0ZWQgZ2VuZXMiKQoKIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBtdXRhdGVkIGdlbmVzIChiYXNlZCBvbiBQQ0dSIHJlcG9ydCkKdGFyZ2V0cyA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJzYW1wbGVfYW5ub3QiXV0KZGF0YSA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXQoKIyMjIyMgQ29uc2lkZXIgb25seSBnZW5lcyB3aXRoIG11dGF0aW9ucyBjYWxzc2lmaWVkIHdpdGhpbiB1c2VyLWRlZmluZWQgdGllcnMKZ2VuZXMgPC0gcmVmX2dlbmVzLmxpc3RbWyJzdW1tYXJ5Il1dJE11dGF0ZWQKCiMjIyMjIERlYWwgd2l0aCBubyBnZW5lcyBvciB3aGVuIG1vcmUgdGhhbiAxMCBnZW5lcyBhcmUgb2YgaW50ZXJlc3QKaWYgKCBsZW5ndGgoZ2VuZXMpID09IDAgKSB7CiAgZ2VuZXMgPC0gTlVMTAogIGxpbWl0X2dlbmVzIDwtIEZBTFNFCiAgZ2VuZXNfbm8gPC0gMAp9IGVsc2UgaWYgKCBsZW5ndGgoZ2VuZXMpID4gcGFyYW1zJHRvcF9nZW5lcyApIHsKICBsaW1pdF9nZW5lcyA8LSBUUlVFCiAgZ2VuZXNfbm8gPC0gcGFyYW1zJHRvcF9nZW5lcwp9IGVsc2UgewogIGxpbWl0X2dlbmVzIDwtIEZBTFNFCiAgZ2VuZXNfbm8gPC0gbGVuZ3RoKGdlbmVzKQp9CgptdXRfZ2VuZXMuZXhwci5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgbXV0X2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJwY2dyIl1dWywgYygiU1lNQk9MIiwgIlRJRVIiLCAiQ09OU0VRVUVOQ0UiLCAiVkFSSUFOVF9DTEFTUyIsICJBRl9UVU1PUiIsICJHRU5PTUlDX0NIQU5HRSIsICJQUk9URUlOX0NIQU5HRSIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGNhbmNlcl9nZW5lcyA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dWywgYygiT25jb2dlbmUiLCAiVFNHIiwgIkZ1c2lvbiIsICJHZXJtbGluZSIpIF0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAicGVyYyIsIHNjYWxpbmcgPSBzY2FsaW5nKQoKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlCm11dF9nZW5lcy5leHByLnBlcmNbWzFdXQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKIyMjIyMgQ3JlYXRlIGRpcmVjdG9yeSBmb3IgdGFibGVzCmlmICggcGFyYW1zJHNhdmVfdGFibGVzICkgewogIHNhdmVXaWRnZXRGaXgod2lkZ2V0PW11dF9nZW5lcy5leHByLnBlcmNbWzFdXSwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJtdXRfZ2VuZXMuZXhwci5wZXJjLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmhpZ2ggZXhwcmVzc2lvbioqIChwZXJjZW50aWxlKSB2YWx1ZXMgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojMDAwMGZmIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojODA4MDgwIj5CTEFOSzwvc3Bhbj4gY2VsbHMgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbmRpY2F0ZSBnZW5lcyB3aXRoICoqbm8vbG93IGV4cHJlc3Npb24qKi4gVGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbiBpbGx1c3RyYXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHBlcmNlbnRpbGVzIGluIHBhdGllbnQgc2FtcGxlIGFuZCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydCBmb3IgZWFjaCBtdXRhdGVkIGdlbmUuIFZhcmlhbnRzJyB0aWVyLCBjb25zZXF1ZW5jZSwgY2xhc3MgYW5kIHR1bW91ciBhbGxlbGUgZnJldXFuZWN5IChBRiksIGFzIHdlbGwgYXMgZ2Vub21pYyBhbmQgcHJvdGVpbiBjaGFuZ2UgYXJlIGFsc28gcHJvdmlkZWQgYmFzZWQgb24gaW5mb3JtYXRpb24gZnJvbSBbUENHUl0oaHR0cHM6Ly9naXRodWIuY29tL3NpZ3Zlbi9wY2dyKXt0YXJnZXQ9Il9ibGFuayJ9IHJlcG9ydC4gSW4gY2FzZSBvZiBtdWx0aXBsZSB2YXJhaW50cyBkZXRlY3RlZCBpbiBzaW5nbGUgZ2VuZSB0aGUgdmFyaWFudCB3aXRoIHRoZSBsb3dlc3QgdGllciBpcyByZXBvcnRlZCBhbmQgb3RoZXIgcG90ZW50aWFsIGNvbnNlcXVlbmNlcyBhcmUgbGlzdGVkIGluIGNvbHVtbiAqQ09OU0VRVUVOQ0VfT1RIRVIqLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKmluY3JlYXNpbmcgdmFyaWFudHMgVElFUioqIGFuZCB0aGVuIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uLgoKPC9mb250Pgo8L2RldGFpbHM+CgpgciBpZiAoIHJ1blBjZ3JDaHVuayAmJiBsZW5ndGgoZ2VuZXMpID4gMjAwMCApIHsgYyhwYXN0ZTAoIjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCB0aGUgdGFibGUgd2FzIHRydW5jYXRlZCB0byAyMDAwIGVudHJpZXMuIikpIH0gZWxzZSB7IGNhdCgiIikgfWAKCioqKgoKIyMjIyBaLXNjb3JlcwoKYGBge3IgbXV0X2dlbmVzX3RhYmxlLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBydW5QY2dyQ2h1bmssIHJlc3VsdHMgPSAiYXNpcyJ9Cm11dF9nZW5lcy5leHByLnogPC0gZXhwclRhYmxlKCBnZW5lcyA9IGdlbmVzLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgZ2VuZXNfYW5ub3QgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXVssIGMoIlNZTUJPTCIsICJFTlNFTUJMIildLCBtdXRfYW5ub3QgPSByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV1bLCBjKCJTWU1CT0wiLCAiVElFUiIsICJDT05TRVFVRU5DRSIsICJWQVJJQU5UX0NMQVNTIiwgIkFGX1RVTU9SIiwgIkdFTk9NSUNfQ0hBTkdFIiwgIlBST1RFSU5fQ0hBTkdFIildLCBvbmNva2JfYW5ub3QgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX29uY29rYiJdXSwgY2FuY2VyX2dlbmVzID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV1bLCBjKCJPbmNvZ2VuZSIsICJUU0ciLCAiRnVzaW9uIiwgIkdlcm1saW5lIikgXSwgZXh0X2xpbmtzID0gVFJVRSwgdHlwZSA9ICJ6Iiwgc2NhbGluZyA9IHNjYWxpbmcpW1sxXV0KCiMjIyMjIFByZXNlbnQgdGhlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZQptdXRfZ2VuZXMuZXhwci56CgojIyMjIyBTYXZlIHRoZSBleHByZXNzaW9uIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1tdXRfZ2VuZXMuZXhwci56LCBmaWxlPXBhc3RlKGV4cHJUYWJsZURpciwgIm11dF9nZW5lcy5leHByLnouaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKG11dF9nZW5lcy5leHByLnopCmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPlJFRDwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipoaWdoIGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6IzAwMDBmZiI+QkxVRTwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipsb3cgZXhwcmVzc2lvbioqIChaLXNjb3JlKSB2YWx1ZXMgaW4gaW5kaXZpZHVhbCBzYW1wbGUgZ3JvdXAuIFRoZSA8c3BhbiBzdHlsZT0iY29sb3I6IzgwODA4MCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW5kaWNhdGUgZ2VuZXMgd2l0aCAqKm5vL2xvdyBleHByZXNzaW9uKiouIFRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW4gaWxsdXN0cmF0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBaLXNjb3JlcyBpbiBwYXRpZW50IHNhbXBsZSBhbmQgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQgZm9yIGVhY2ggbXV0YXRlZCBnZW5lLiBWYXJpYW50cycgdGllciwgY29uc2VxdWVuY2UsIGNsYXNzIGFuZCB0dW1vdXIgYWxsZWxlIGZyZXVxbmVjeSAoQUYpLCBhcyB3ZWxsIGFzIGdlbm9taWMgYW5kIHByb3RlaW4gY2hhbmdlIGFyZSBhbHNvIHByb3ZpZGVkIGJhc2VkIG9uIGluZm9ybWF0aW9uIGZyb20gW1BDR1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaWd2ZW4vcGNncil7dGFyZ2V0PSJfYmxhbmsifSByZXBvcnQuIEluIGNhc2Ugb2YgbXVsdGlwbGUgdmFyYWludHMgZGV0ZWN0ZWQgaW4gc2luZ2xlIGdlbmUgdGhlIHZhcmlhbnQgd2l0aCB0aGUgbG93ZXN0IHRpZXIgaXMgcmVwb3J0ZWQgYW5kIG90aGVyIHBvdGVudGlhbCBjb25zZXF1ZW5jZXMgYXJlIGxpc3RlZCBpbiBjb2x1bW4gKkNPTlNFUVVFTkNFX09USEVSKi4gR2VuZXMgYXJlIG9yZGVyZWQgYnkgKippbmNyZWFzaW5nIHZhcmlhbnRzIFRJRVIqKiBhbmQgdGhlbiBieSAqKmRlY3JlYXNpbmcqKiBhYnNvbHV0ZSB2YWx1ZXMgaW4gdGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbi4KCjwvZm9udD4KPC9kZXRhaWxzPgoKYHIgaWYgKCBydW5QY2dyQ2h1bmsgJiYgbGVuZ3RoKGdlbmVzKSA+IDIwMDAgKSB7IGMocGFzdGUwKCI8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj5OT1RFPC9zcGFuPiwgdGhlIHRhYmxlIHdhcyB0cnVuY2F0ZWQgdG8gMjAwMCBlbnRyaWVzLiIpKSB9IGVsc2UgeyBjYXQoIiIpIH1gCgoqKioKCiMjIyAtIEV4cHJlc3Npb24gcHJvZmlsZXMgey50YWJzZXR9CgpgciBpZiAoIGV4aXN0cygibGltaXRfZ2VuZXMiKSApIHsgaWYgKCBsaW1pdF9nZW5lcyApIHsgYyhwYXN0ZTAoIkV4cHJlc3Npb24gcHJvZmlsZXMgZm9yICIsIGdlbmVzX25vLCAiIG11dGF0ZWQgZ2VuZXMgd2l0aCB2YXJpYW50cyBhbm5vdGF0ZWQgd2l0aCB0aGUgbG93ZXN0IFt0aWVyXShodHRwczovL3BjZ3IucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L3RpZXJfc3lzdGVtcy5odG1sI3RpZXItbW9kZWwtMi1wY2dyLWFjbWcpe3RhcmdldD1cIl9ibGFua1wifSBhbmQgZGVtb25zdHJhdGluZyB0aGUgZ3JlYXRlc3QgZGlmZmVyZW5jZSBpbiBtUk5BIGV4cHJlc3Npb24gKHBlcmNlbnRpbGUpIHZhbHVlcyBiZXR3ZWVuIHBhdGllbnQncyBzYW1wbGUgYW5kIHRoZSBhdmVyYWdlIG1STkEgZXhwcmVzc2lvbiBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIHBhdGllbnRzLiIpKSB9IGVsc2UgeyBjYXQoIiAiKSB9fWAKCmBgYHtyIGNkZl9wbG90c19tdXQsIGVjaG89RkFMU0UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIGV2YWwgPSBydW5QY2dyQ2h1bmssIHJlc3VsdHM9ImFzaXMifQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGxvdGx5KSkKIyMjIyMgR2VuZXJhdGUgZW1waXJpY2FsIGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uIGZ1bmN0aW9uIChFQ0RGKSBwbG90IGlsbHVzdHJhdGluZyBtUk5BIGV4cHJlc3Npb24gbGV2ZWwgZm9yIHRoZSBnZW5lcyBvZiBpbnRlcmVzdCBpbiB0aGUgY29udGV4dCBvZiB0aGUgb3ZlcmFsbCBtUk5BIGV4cHJlc3Npb24gZGlzdHJpYnV0aW9uCm91dHB1dF9jZGYgPC0gbGlzdCgpCm91dHB1dF9jb3VudHMgPC0gbGlzdCgpCm91dHB1dF9kZW5zaXR5IDwtIGxpc3QoKQpnZW5lcyA8LSBtdXRfZ2VuZXMuZXhwci5wZXJjW1syXV0kU1lNQk9MCgojIyMjIyBGb3IgZWFjaCBnZW5lIGdlbmVyYXRlICgxKSBDREYgcGxvdCBhbmQgYWRkIGJveHBsb3QgYmVsb3cgdG8gc2hvdyB0aGUgZGF0YSB2YXJpYW5jZSBmb3Igc2VsZWN0ZWQgZ2VuZSBpbiBpbmRpdmlkdWFsIGdyb3VwcywgKDIpIGJhci1wbG90IG9mIHJlYWQgY291bnQgZGF0YSBhY3Jvc3MgYWxsIHNhbXBsZXMgYW5kICgzKSBkZW5zaXR5IHBsb3QgdG8gZGVtb25zdHJhdGUgZXhwcmVzc2lvbiBkaXN0cmlidXRpb24gaW4gaW52ZXN0aWdhdGVkIHNhbXBsZSAKZm9yKCBpIGluIDE6Z2VuZXNfbm8gKSB7CiAgaWYgKCBnZW5lc19ubyA+IDAgJiYgZ2VuZXNbaV0gJWluJSByb3duYW1lcyhkYXRhKSApIHsKICAgIAogICAgIyMjIyMgQ0RGIHBsb3QKICAgIG91dHB1dF9jZGZbW2ldXSA8LSBjZGZQbG90KGdlbmUgPSBnZW5lc1tpXSwgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGFkZEJveFBsb3QgPSBUUlVFLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQogICAgCiAgICAjIyMjIyBCYXItcGxvdCBvZiByZWFkIGNvdW50cwogICAgIyMjIyMgRmlyc3QgbWFwIHRoZSBnZW5lIHN5bWJvbCB0byBFbnNtZWJsIElEICh1c2VkIGluIHRoZSBjb3VudHMgZGF0YSkKICAgIGdlbmVzLkVOU0VNQkwgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV0kRU5TRU1CTFsgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV0kU1lNQk9MID09ICBnZW5lc1tpXSBdCiAgICAKICAgIG91dHB1dF9jb3VudHNbW2ldXSA8LSBiYXJQbG90KGdlbmUgPSBnZW5lcy5FTlNFTUJMLCBkYXRhID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSwgeV90aXRsZSA9ICJDb3VudHMiLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwICkKICAgIAogICAgIyMjIyMgRGVuc2l0eSBwbG90IC0gZXhwcmVzc2lvbiBkaXN0cmlidXRpb24KICAgIG91dHB1dF9kZW5zaXR5W1tpXV0gPC0gZGVuc2l0eVBsb3QoZ2VuZSA9IGdlbmVzW2ldLCBkYXRhID0gZGF0YSwgbWFpbl90aXRsZT0gIiIsIHhfdGl0bGUgPSAiWi1zY29yZSIsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZGlzdHJpYnV0aW9ucyA9IGMoIm5vcm1hbCIsICJiaW1vZGFsIiksIHNjYWxpbmcgPSBzY2FsaW5nKSAKICB9CiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCn0KCiMjIyMjIE5vdyBvbmNlIHRoZSBwbG90cyBhcmUgcmVhZHkgc2hvdyB0aGVtIGluIHNlcGFyYXRlIHRhYnMKaWYgKCBnZW5lc19ubyAhPSAwICkgewogIGZvciggaSBpbiAxOmdlbmVzX25vICl7CiAgICBpZiAoIGdlbmVzW2ldICVpbiUgcm93bmFtZXMoZGF0YSkgKSB7CiAgICAgIGNhdCgiXG4jIyMjICIsIGdlbmVzW2ldLCAiXG4iKQogICAgICBjYXQocmVuZGVyVGFncyhvdXRwdXRfY2RmW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5QbG90IGxlZ2VuZDwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIioqVG9wIHBhbmVsKio6IGRpc3RyaWJ1dGlvbiBvZiBwZXJjZW50aWxlIHZhbHVlcyAoKnktYXhpcyopIGFzIGEgZnVuY3Rpb24gb2YgZXhwcmVzc2lvbiBsZXZlbHMgKFotc2NvcmVzLCAqeC1heGlzKikgZm9yICoiLCBnZW5lc1tpXSwgIiogaW4gcGF0aWVudCdzIHNhbXBsZSAoKmJsYWNrIGRvdCopIGFuZCBvdGhlciByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydChzKSAobWVkaWFuIHZhbHVlKHMpKS5cblxuIikpCiAgICAgIGNhdChwYXN0ZTAoIioqQm90dG9tIHBhbmVsKio6IGJveC1wbG90IHByZXNlbnRpbmcgZXhwcmVzc2lvbiBsZXZlbCAoWi1zY29yZSkgb2YgKiIsIGdlbmVzW2ldLCAiKiBpbiBwYXRpZW50J3Mgc2FtcGxlICgqYmxhY2sgZG90KikgYW5kIGl0cyBleHByZXNzaW9uIGxldmVscyBvYnNlcnZlZCBhY3Jvc3Mgc2FtcGxlcyBmcm9tIG90aGVyIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0KHMpLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxkZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxzdW1tYXJ5PlJlYWQgY291bnRzPC9zdW1tYXJ5PlxuIikKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NvdW50c1tbaV1dKSRodG1sKQogICAgICBjYXQoIjxmb250IHNpemU9XCIyXCI+XG4iKQogICAgICBjYXQocGFzdGUwKCJCYXItcGxvdCBpbGx1c3RyYXRpbmcgcmVhZCBjb3VudHMgZm9yICoiLCBnZW5lc1tpXSwgIiogYWNyb3NzIGFsbCBzYW1wbGVzLiBUaGUgKiIsIGdlbmVzW2ldLCAiKiByZWFkIGNvdW50IGluIHBhdGllbnQncyBzYW1wbGUgaXMgaW5kaWNhdGVkIGJ5ICpibGFjayBiYXIqLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxkZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxzdW1tYXJ5PkV4cHJlc3Npb24gZGlzdHJpYnV0aW9uIHBhdHRlcm5zPC9zdW1tYXJ5PlxuIikKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2RlbnNpdHlbW2ldXSkkaHRtbCkKICAgICAgY2F0KCI8Zm9udCBzaXplPVwiMlwiPlxuIikKICAgICAgY2F0KHBhc3RlMCgiUGxvdCBpbGx1c3RyYXRpbmcgZGlzdHJpYnV0aW9uIG9mIGV4cHJlc3Npb24gbGV2ZWxzIChaLXNjb3Jlcykgb2YgKiIsIGdlbmVzW2ldLCAiKiAqb2JzZXJ2ZWQqIGFjcm9zcyBhbGwgc2FtcGxlcyBhbG9uZyB3aXRoIHNpbXVsYXRlZCAqbm9ybWFsKiBhbmQgKmJpbW9kYWwqIGRpc3RyaWJ1dGlvbnMuIFRoZSAqIiwgZ2VuZXNbaV0sICIqIGV4cHJlc3Npb24gbGV2ZWwgb2JzZXJ2ZWQgaW4gcGF0aWVudCdzIHNhbXBsZSBpcyBpbmRpY2F0ZWQgYnkgKmJsYWNrIGRvdCogaW4gZWFjaCBkaXN0cmlidXRpb24uXG4iKSkKICAgICAgY2F0KCJcbjwvZm9udD5cbiIpCiAgICAgIGNhdCgiXG48L2RldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbioqKlxuIikKICAgIH0gZWxzZSB7CiAgICAgIGNhdCgiXG4jIyMjICIsIGdlbmVzW2ldLCAiXG4iKQogICAgICBjYXQoIlxuPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+Tk9URTwvc3Bhbj4sIGV4cHJlc3Npb24gZGF0YSBpcyBub3QgYXZhaWxhYmxlIGZvciB0aGF0IGdlbmUuXG4iKQogICAgICBjYXQoIlxuKioqXG4iKQogICAgfQogIH0KICAjIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKICAKfSBlbHNlIHsKICBjYXQoIlxuTm8gYWx0ZXJhdGlvbnMgd2VyZSByZXBvcnRlZC5cbiIpCiAgIGNhdCgiXG4qKipcbiIpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0obGlzdCA9IGxzKHBhdHRlcm49J15vdXRwdXQqJykpCnJtKGxpbWl0X2dlbmVzKQpgYGAKCmByIGlmICggIXJ1blBjZ3JDaHVuayApIHsgYygiKioqIikgfSBlbHNlIHsgYygiICIpIH1gCgojIyBGdXNpb24gZ2VuZXMKCjxkZXRhaWxzPgo8c3VtbWFyeT5GdXNpb24gZ2VuZXMgcHJpb3JpdGlzYXRpb248L3N1bW1hcnk+CgpGdXNpb24gZ2VuZXMgZGV0ZWN0ZWQgaW4gdHJhbnNjcmlwdG9tZSBkYXRhIGFyZSBwcmlvcml0aXNlZCBpbiB0aGUgZm9sbG93aW5nIG9yZGVyOgoKMS4gSW52b2x2ZW1lbnQgb2YgZnVzaW9uIGdlbmUocykgKipkZXRlY3RlZCBpbiBnZW5vbWljIGRhdGEqKiAoaWYgW1N0cnVjdHVyYWwgdmFyaWFudHNdIHJlc3VsdHMgYXJlIGF2YWlsYWJsZSkKCjIuICoqRGV0ZWN0ZWQgaW4gdHJhbnNjcmlwdG9tZSBkYXRhKiogYnkgW0FycmliYV0oaHR0cHM6Ly9hcnJpYmEucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0Lyl7dGFyZ2V0PSJfYmxhbmsifSB0b29sCgozLiAqKlJlcG9ydGVkKiogZnVzaW9uIGV2ZW50IGFjY29yZGluZyB0byBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn0gZGF0YWJhc2UKCjQuIERlY3JlYXNpbmcgbnVtYmVyIG9mICoqc3BsaXQgcmVhZHMqKgoKNS4gRGVjcmVhc2luZyBudW1iZXIgb2YgKipwYWlyIHJlYWRzKioKCjYuIEludm9sdmVtZW50IG9mICoqY2FuY2VyIGdlbmUocykqKiAoc2VlIFtDYW5jZXIgZ2VuZXNdIHNlY3Rpb24pCgo8L2RldGFpbHM+Cgo8ZGV0YWlscz4KPHN1bW1hcnk+RnVzaW9uIGdlbmVzIGZpbHRlcmluZzwvc3VtbWFyeT4KCkZ1c2lvbiBnZW5lcyBkZXRlY3RlZCBpbiB0cmFuc2NyaXB0b21lIGRhdGEgYXJlIHJlcG9ydGVkIGlmICoqYXQgbGVhc3Qgb25lKiogb2YgdGhlIGZvbGxvd2luZyBjcml0ZXJpYSBpcyBtZXQ6CgoxLiBJbnZvbHZlbWVudCBvZiBmdXNpb24gZ2VuZShzKSAqKmRldGVjdGVkIGluIGdlbm9taWMgZGF0YSoqIChpZiBbU3RydWN0dXJhbCB2YXJpYW50c10gcmVzdWx0cyBhcmUgYXZhaWxhYmxlKQoKMi4gKipSZXBvcnRlZCoqIGZ1c2lvbiBldmVudCBhY2NvcmRpbmcgdG8gW0Z1c2lvbkdEQl0oaHR0cHM6Ly9jY3NtLnV0aC5lZHUvRnVzaW9uR0RCKXt0YXJnZXQ9Il9ibGFuayJ9IGRhdGFiYXNlCgozLiBJbnZvbHZlbWVudCBvZiAqKmNhbmNlciBnZW5lKHMpKiogKHNlZSBbQ2FuY2VyIGdlbmVzXSBzZWN0aW9uKQoKNC4gKipTcGxpdCByZWFkcyoqID4gMQoKNS4gKipQYWlyIHJlYWRzKiogPiAxIGFuZCAqKnNwbGl0IHJlYWRzKiogPiAxCgo8L2RldGFpbHM+CgpgciBpZiAoICFydW5GdXNpb25DaHVuayApIHsgYygiRnVzaW9uIGdlbmVzIGluZm9ybWF0aW9uIGZvciB0aGlzIHNhbXBsZSBpcyAqKk5PVCBBVkFJTEFCTEUqKi4iKSB9YAoKIyMjIC0gU3VtbWFyeQoKT3V0IG9mIHRoZSBgciBpZiAoIHJ1bkZ1c2lvbkNodW5rICkgeyBucm93KGZ1c2lvbnMpIH0gZWxzZSB7IGMoIjAiKSB9YCBmdXNpb24gZXZlbnQocykgPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPioqYHIgaWYgKCBydW5GdXNpb25DaHVuayApIHsgbnJvdyhmdXNpb25zWyBmdXNpb25zJGdlbmVBX2RuYV9zdXBwb3J0ID09ICJZZXMiIHwgZnVzaW9ucyRnZW5lQl9kbmFfc3VwcG9ydCA9PSAiWWVzIiAsIF0pIH0gZWxzZSB7IGMoIjAiKSB9YCoqPC9zcGFuPiBpbnZvbHZlICoqRE5BLXN1cHBvcnRlZCoqIGZ1c2lvbiBnZW5lcyAoc2VlIFtTdHJ1Y3R1cmFsIHZhcmlhbnRzXSBzZWN0aW9uKSwgPHNwYW4gc3R5bGU9ImNvbG9yOiMwMmQ2NTMiPioqYHIgaWYgKCBydW5GdXNpb25DaHVuayApIHsgbnJvdyhmdXNpb25zWyBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbiA9PSAiWWVzIiAsIF0pIH0gZWxzZSB7IGMoIjAiKSB9YCoqPC9zcGFuPiBhcmUgKipyZXBvcnRlZCBpbiBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn0qKiBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiM3Njc2ODkiPioqYHIgaWYgKCBydW5GdXNpb25DaHVuayApIHsgbnJvdyhmdXNpb25zWyBmdXNpb25zJGZ1c2lvbnNfY2FuY2VyID09ICJZZXMiICwgXSkgfSBlbHNlIHsgYygiMCIpIH1gKio8L3NwYW4+IGludm9sdmUgKipbQ2FuY2VyIGdlbmVzXSoqLgoKKipgciBpZiAoICFydW5GdXNpb25DaHVuayApIHsgYygiTm8gZnVzaW9uIGdlbmVzIHdlcmUgZGV0ZWN0ZWQhIikgfWAqKgoKYGBge3IgZnVzaW9uc19zdW1tYXJ5X3RhYmxlLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIENyZWF0ZSBhIG5pY2UgdGFibGUgb3V0cHV0ICh3aXRoIGRhdGFUYWJsZSkKaWYgKCBydW5GdXNpb25DaHVuayApIHsKICAKICAjIyMjIyBVcGRhdGUgTXlTUUwgY29tbWVuZCB0byBwb3B1bGF0ZSBSTkEtc2VxIGRhdGEgcG9ydGFsCiAgbXlzcWxfcG9wdWxhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlLCAiLEZ1c2lvbiBnZW5lcyIpCiAgbXlzcWxfcG9wdWxhdGVfdXBkYXRlIDwtIHBhc3RlMChteXNxbF9wb3B1bGF0ZV91cGRhdGUsICIsRnVzaW9uIGdlbmVzIikKICAKICBmdXNpb25zLnRhYmxlIDwtIGZ1c2lvbnMKICBmdXNpb25zLnRhYmxlJGdlbmVBIDwtIGFzLnZlY3RvcihmdXNpb25zLnRhYmxlJGdlbmVBKQogIGZ1c2lvbnMudGFibGUkZ2VuZUIgPC0gYXMudmVjdG9yKGZ1c2lvbnMudGFibGUkZ2VuZUIpCiAgCiAgIyMjIyMgUHJvdmlkZSBsaW5rIHRvIEZ1c2lvbkdEQgogIGZvciAoIGkgaW4gMTpucm93KGZ1c2lvbnMudGFibGUpICkgewogICAgICBpZiAoIGZ1c2lvbnMudGFibGUkcmVwb3J0ZWRfZnVzaW9uW2ldID09ICJZZXMiICkgewogICAgICAgIGZ1c2lvbnMudGFibGUkZ2VuZUFbaV0gPC0gcGFzdGUwKCI8YSBocmVmPSdodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIvZ2VuZV9zZWFyY2hfcmVzdWx0LmNnaT9wYWdlPXBhZ2UmdHlwZT1xdWlja19zZWFyY2gmcXVpY2tfc2VhcmNoPSIsIGZ1c2lvbnMudGFibGUkRkdJRFtpXSwgIicgdGFyZ2V0PSdfYmxhbmsnPiIsIGZ1c2lvbnMudGFibGUkZ2VuZUFbaV0sICI8L2E+IikKICAKICAgICAgICBmdXNpb25zLnRhYmxlJGdlbmVCW2ldIDwtIHBhc3RlMCgiPGEgaHJlZj0naHR0cHM6Ly9jY3NtLnV0aC5lZHUvRnVzaW9uR0RCL2dlbmVfc2VhcmNoX3Jlc3VsdC5jZ2k/cGFnZT1wYWdlJnR5cGU9cXVpY2tfc2VhcmNoJnF1aWNrX3NlYXJjaD0iLCBmdXNpb25zLnRhYmxlJEZHSURbaV0sICInIHRhcmdldD0nX2JsYW5rJz4iLCBmdXNpb25zLnRhYmxlJGdlbmVCW2ldLCAiPC9hPiIpCiAgICAgIH0KICB9CiAgCiAgIyMjIyMgRHJhZ2VuICsgQXJyaWJhCiAgaWYgKCBydW5EcmFnZW5GdXNpb25DaHVuayAmJiBydW5BcnJpYmFDaHVuayApIHsKICAgIGZ1c2lvbnMudGFibGUgPC0gZnVzaW9ucy50YWJsZVsgLCBuYW1lcyhmdXNpb25zLnRhYmxlKSAlIWluJSAiRkdJRCIgXQogICAgZnVzaW9ucy50YWJsZSA8LSBmdXNpb25zLnRhYmxlWyAsIGMoImdlbmVBIiwgImdlbmVCIiwgInNwbGl0X3JlYWRzIiwgInNwbGl0X3JlYWRzQSIsICJzcGxpdF9yZWFkc0IiLCAiZGlzY29yZGFudF9tYXRlcyIsICJnZW5lQV9kbmFfc3VwcG9ydCIsICJnZW5lQl9kbmFfc3VwcG9ydCIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIsICJjb25maWRlbmNlIiwgInNjb3JlIiwgImJyZWFrcG9pbnRBIiwgImJyZWFrcG9pbnRCIiwgInNpdGVBIiwgInNpdGVCIiwgInR5cGUiLCAiY2lyY29zIildCiAgbmFtZXMoZnVzaW9ucy50YWJsZSkgPC0gYygiR2VuZSBBIiwgIkdlbmUgQiIsICJTcGxpdCByZWFkcyAoVG90YWwpIiwgIlNwbGl0IHJlYWRzIChBKSIsICJTcGxpdCByZWFkcyAoQikiLCAiUGFpciByZWFkcyIsICJETkEgc3VwcG9ydCAoQSkiLCAiRE5BIHN1cHBvcnQgKEIpIiwgIlJlcG9ydGVkIGZ1c2lvbiIsICJDYW5jZXIgZ2VuZShzKSIsICJGdXNpb24gZ2VuZSAoQSkiLCAiRnVzaW9uIGdlbmUgKEIpIiwgIkNvbmZpZGVuY2UgKEFycmliYSkiLCAiU2NvcmUgKERyYWdlbikiLCAiQnJlYWtwb2ludCAoQSkiLCAiQnJlYWtwb2ludCAoQikiLCAiU2l0ZSAoQSkiLCAiU2l0ZSAoQikiLCAiVHlwZSIsICJHZW5vbWljIHZpZXciKQogIAogICMjIyMjIEFycmliYSAvIEFycmliYSArIFBpenpseQogIH0gZWxzZSBpZiAoIHJ1bkFycmliYUNodW5rICkgewogICAgZnVzaW9ucy50YWJsZSA8LSBmdXNpb25zLnRhYmxlWyAsIG5hbWVzKGZ1c2lvbnMudGFibGUpICUhaW4lICJGR0lEIiBdCiAgICBmdXNpb25zLnRhYmxlIDwtIGZ1c2lvbnMudGFibGVbICwgYygiZ2VuZUEiLCAiZ2VuZUIiLCAic3BsaXRfcmVhZHMiLCAic3BsaXRfcmVhZHNBIiwgInNwbGl0X3JlYWRzQiIsICJkaXNjb3JkYW50X21hdGVzIiwgImdlbmVBX2RuYV9zdXBwb3J0IiwgImdlbmVCX2RuYV9zdXBwb3J0IiwgInJlcG9ydGVkX2Z1c2lvbiIsICJmdXNpb25zX2NhbmNlciIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUEiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVCIiwgImNvbmZpZGVuY2UiLCAiYnJlYWtwb2ludEEiLCAiYnJlYWtwb2ludEIiLCAic2l0ZUEiLCAic2l0ZUIiLCAidHlwZSIsICJjaXJjb3MiKV0KICBuYW1lcyhmdXNpb25zLnRhYmxlKSA8LSBjKCJHZW5lIEEiLCAiR2VuZSBCIiwgIlNwbGl0IHJlYWRzIChUb3RhbCkiLCAiU3BsaXQgcmVhZHMgKEEpIiwgIlNwbGl0IHJlYWRzIChCKSIsICJQYWlyIHJlYWRzIiwgIkROQSBzdXBwb3J0IChBKSIsICJETkEgc3VwcG9ydCAoQikiLCAiUmVwb3J0ZWQgZnVzaW9uIiwgIkNhbmNlciBnZW5lKHMpIiwgIkZ1c2lvbiBnZW5lIChBKSIsICJGdXNpb24gZ2VuZSAoQikiLCAiQ29uZmlkZW5jZSAoQXJyaWJhKSIsICJCcmVha3BvaW50IChBKSIsICJCcmVha3BvaW50IChCKSIsICJTaXRlIChBKSIsICJTaXRlIChCKSIsICJUeXBlIiwgIkdlbm9taWMgdmlldyIpCiAgCiAgIyMjIyMgRHJhZ2VuIG9ubHkKICB9IGVsc2UgaWYgKCBydW5EcmFnZW5GdXNpb25DaHVuayApIHsKICAgIAogICAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgdmVyc2lvbiAzLjkuMwogICAgaWYgKCBhbGwoYygiR2VuZUFMb2NhdGlvbiIsICJHZW5lQkxvY2F0aW9uIiwgIk51bVNwbGl0UmVhZHMiLCJOdW1Tb2Z0Q2xpcHBlZFJlYWRzIiwgIlNjb3JlIikgJWluJSBjb2xuYW1lcyhkcmFnZW4uZnVzaW9ucykpICkgewogICAgICAgIGZ1c2lvbnMudGFibGUgPC0gZnVzaW9ucy50YWJsZVsgLCBuYW1lcyhmdXNpb25zLnRhYmxlKSAlIWluJSAiRkdJRCIgXQogICAgICAgIGZ1c2lvbnMudGFibGUgPC0gZnVzaW9ucy50YWJsZVsgLCBjKCJnZW5lQSIsICJnZW5lQiIsICJzcGxpdF9yZWFkcyIsICJnZW5lQV9kbmFfc3VwcG9ydCIsICJnZW5lQl9kbmFfc3VwcG9ydCIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVBIiwgInJlcG9ydGVkX2Z1c2lvbl9nZW5lQiIsICJzY29yZSIsICJicmVha3BvaW50QSIsICJicmVha3BvaW50QiIsICJzaXRlQSIsICJzaXRlQiIsICJjaXJjb3MiKV0KICAgICAgbmFtZXMoZnVzaW9ucy50YWJsZSkgPC0gYygiR2VuZSBBIiwgIkdlbmUgQiIsICJTcGxpdCByZWFkcyIsICJETkEgc3VwcG9ydCAoQSkiLCAiRE5BIHN1cHBvcnQgKEIpIiwgIlJlcG9ydGVkIGZ1c2lvbiIsICJDYW5jZXIgZ2VuZShzKSIsICJGdXNpb24gZ2VuZSAoQSkiLCAiRnVzaW9uIGdlbmUgKEIpIiwgIlNjb3JlIiwgIkJyZWFrcG9pbnQgKEEpIiwgIkJyZWFrcG9pbnQgKEIpIiwgIlNpdGUgKEEpIiwgIlNpdGUgKEIpIiwgIkdlbm9taWMgdmlldyIpCiAgICAgIAogICAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgcHJpb3IgdG8gdmVyc2lvbiAzLjkuMwogICAgfSBlbHNlIHsKICAgICAgZnVzaW9ucy50YWJsZSA8LSBmdXNpb25zLnRhYmxlWyAsIG5hbWVzKGZ1c2lvbnMudGFibGUpICUhaW4lICJGR0lEIiBdCiAgICAgICAgZnVzaW9ucy50YWJsZSA8LSBmdXNpb25zLnRhYmxlWyAsIGMoImdlbmVBIiwgImdlbmVCIiwgImdlbmVBX2RuYV9zdXBwb3J0IiwgImdlbmVCX2RuYV9zdXBwb3J0IiwgInJlcG9ydGVkX2Z1c2lvbiIsICJmdXNpb25zX2NhbmNlciIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUEiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVCIiwgInNjb3JlIiwgImJyZWFrcG9pbnRBIiwgImJyZWFrcG9pbnRCIiwgImNpcmNvcyIpXQogICAgICBuYW1lcyhmdXNpb25zLnRhYmxlKSA8LSBjKCJHZW5lIEEiLCAiR2VuZSBCIiwgIkROQSBzdXBwb3J0IChBKSIsICJETkEgc3VwcG9ydCAoQikiLCAiUmVwb3J0ZWQgZnVzaW9uIiwgIkNhbmNlciBnZW5lKHMpIiwgIkZ1c2lvbiBnZW5lIChBKSIsICJGdXNpb24gZ2VuZSAoQikiLCAiU2NvcmUiLCAiQnJlYWtwb2ludCAoQSkiLCAiQnJlYWtwb2ludCAoQikiLCAiR2Vub21pYyB2aWV3IikKICAgIH0KICAKICAjIyMjIyBQaXp6bHkgb25seQogIH0gZWxzZSB7CiAgICBmdXNpb25zLnRhYmxlIDwtIGZ1c2lvbnMudGFibGVbICwgbmFtZXMoZnVzaW9ucy50YWJsZSkgJSFpbiUgIkZHSUQiIF0KICAgIGZ1c2lvbnMudGFibGUgPC0gZnVzaW9ucy50YWJsZVsgLCBjKCJnZW5lQSIsICJnZW5lQiIsICJzcGxpdF9yZWFkcyIsICJkaXNjb3JkYW50X21hdGVzIiwgImdlbmVBX2RuYV9zdXBwb3J0IiwgImdlbmVCX2RuYV9zdXBwb3J0IiwgInJlcG9ydGVkX2Z1c2lvbiIsICJmdXNpb25zX2NhbmNlciIsICJyZXBvcnRlZF9mdXNpb25fZ2VuZUEiLCAicmVwb3J0ZWRfZnVzaW9uX2dlbmVCIiwgImNpcmNvcyIpXQogIG5hbWVzKGZ1c2lvbnMudGFibGUpIDwtIGMoIkdlbmUgQSIsICJHZW5lIEIiLCAiU3BsaXQgcmVhZHMiLCAiUGFpciByZWFkcyIsICJETkEgc3VwcG9ydCAoQSkiLCAiRE5BIHN1cHBvcnQgKEIpIiwgIlJlcG9ydGVkIGZ1c2lvbiIsICJDYW5jZXIgZ2VuZShzKSIsICJGdXNpb24gZ2VuZSAoQSkiLCAiRnVzaW9uIGdlbmUgKEIpIiwgIkdlbm9taWMgdmlldyIpCiAgfQogIAogICMjIyMjIFByZXNlbnQgZ2VuZSBmdXNpb24gZXZlbnRzIGluIGEgdGFibGUKICBmdXNpb25zLnN1bW1hcnkgPC0gRFQ6OmRhdGF0YWJsZSggZGF0YSA9IGZ1c2lvbnMudGFibGUsIGZpbHRlciA9ICJub25lIiwgcm93bmFtZXMgPSBGQUxTRSwgZXh0ZW5zaW9ucyA9IGMoJ0J1dHRvbnMnLCdTY3JvbGxlcicpLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTAsIGRvbSA9ICdCZnJ0aXAnLCBidXR0b25zID0gYygnZXhjZWwnLCAnY3N2JywgJ3BkZicsJ2NvcHknLCdjb2x2aXMnKSwgc2Nyb2xsWCA9IFRSVUUsIGRlZmVyUmVuZGVyID0gVFJVRSwgc2Nyb2xsWSA9ICIzMzNweCIsIHNjcm9sbGVyID0gVFJVRSksIHdpZHRoID0gODAwLCBoZWlnaHQgPSA0OTAsIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbihzdHlsZSA9ICdjYXB0aW9uLXNpZGU6IHRvcDsgdGV4dC1hbGlnbjogbGVmdDsgY29sb3I6Z3JleTsgZm9udC1zaXplOjEwMCUgOycpLCBlc2NhcGUgPSBGQUxTRSkgJT4lCiAgICAgIERUOjpmb3JtYXRTdHlsZSggY29sdW1ucyA9IG5hbWVzKGZ1c2lvbnMudGFibGUpLCBgZm9udC1zaXplYCA9ICcxMnB4JywgJ3RleHQtYWxpZ24nID0gJ2NlbnRlcicgKSAlPiUKICAgIAogICAgICAjIyMjIyBIaWdobGlnaHQgcm93cyB3aXRoIGZ1c2lvbnMgaW52b2x2aW5nIGNhbmNlciBnZW5lcyAoZ3JleSkgb3IgRE5BIHN1cHBvcnQgKGZyb20gTUFOVEEsIG9yYW5nZSkKICAgICAgRFQ6OmZvcm1hdFN0eWxlKCBjb2x1bW5zID0gY29sbmFtZXMoZnVzaW9ucy50YWJsZSkgJWluJSAiQ2FuY2VyIGdlbmUocykiLCBiYWNrZ3JvdW5kQ29sb3IgPSBEVDo6c3R5bGVFcXVhbChjKCItIiwgIlllcyIpLCBjKCd0cmFuc3BhcmVudCcsICdsaWdodGdyZXknKSkgKSAlPiUKICAgICAgRFQ6OmZvcm1hdFN0eWxlKCBjb2x1bW5zID0gY29sbmFtZXMoZnVzaW9ucy50YWJsZSkgJWluJSAiRE5BIHN1cHBvcnQgKEEpIiwgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlRXF1YWwoYygiLSIsICJZZXMiKSwgYygndHJhbnNwYXJlbnQnLCAnY29yYWwnKSkgKSAlPiUKICAgICAgRFQ6OmZvcm1hdFN0eWxlKCBjb2x1bW5zID0gY29sbmFtZXMoZnVzaW9ucy50YWJsZSkgJWluJSAiRE5BIHN1cHBvcnQgKEIpIiwgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlRXF1YWwoYygiLSIsICJZZXMiKSwgYygndHJhbnNwYXJlbnQnLCAnY29yYWwnKSkgKSAlPiUKICAgICAgRFQ6OmZvcm1hdFN0eWxlKCBjb2x1bW5zID0gY29sbmFtZXMoZnVzaW9ucy50YWJsZSkgJWluJSAiUmVwb3J0ZWQgZnVzaW9uIiwgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlRXF1YWwoIGMoIi0iLCAiWWVzIiksIGMoJ3RyYW5zcGFyZW50JywgJ2xpZ2h0Z3JlZW4nKSkgKQogIAogIGZ1c2lvbnMuc3VtbWFyeQogIAp9IGVsc2UgewogIAogICMjIyMjIENyZWF0ZSBlbXB0eSB0YWJsZQogIGZ1c2lvbnMudGFibGUgPC0gZGF0YS5mcmFtZShtYXRyaXgobmNvbCA9IDE4LCBucm93ID0gMCkpCiAgCiAgbmFtZXMoZnVzaW9ucy50YWJsZSkgPC0gYygiR2VuZSBBIiwgIkdlbmUgQiIsICJTcGxpdCByZWFkcyIsICJQYWlyIHJlYWRzIiwgIkROQSBzdXBwb3J0IChBKSIsICJETkEgc3VwcG9ydCAoQikiLCAiUmVwb3J0ZWQgZnVzaW9uIiwgIkNhbmNlciBnZW5lKHMpIiwgIkZ1c2lvbiBnZW5lIChBKSIsICJGdXNpb24gZ2VuZSAoQikiLCAiQnJlYWtwb2ludCAoQSkiLCAiQnJlYWtwb2ludCAoQikiLCAiR2Vub21pYyB2aWV3IikKCiAgIyMjIyMgUHJlc2VudCBnZW5lIGZ1c2lvbiBldmVudHMgaW4gYSB0YWJsZQogIGZ1c2lvbnMuc3VtbWFyeSA8LSBEVDo6ZGF0YXRhYmxlKCBkYXRhID0gZnVzaW9ucy50YWJsZSwgZmlsdGVyID0gIm5vbmUiLCByb3duYW1lcyA9IEZBTFNFLCBleHRlbnNpb25zID0gYygnQnV0dG9ucycsJ1Njcm9sbGVyJyksIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMCwgZG9tID0gJ0JmcnRpcCcsIGJ1dHRvbnMgPSBjKCdleGNlbCcsICdjc3YnLCAncGRmJywnY29weScsJ2NvbHZpcycpLCBzY3JvbGxYID0gVFJVRSwgZGVmZXJSZW5kZXIgPSBUUlVFLCBzY3JvbGxZID0gIjMzM3B4Iiwgc2Nyb2xsZXIgPSBUUlVFKSwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQ5MCwgY2FwdGlvbiA9IGh0bWx0b29sczo6dGFncyRjYXB0aW9uKHN0eWxlID0gJ2NhcHRpb24tc2lkZTogdG9wOyB0ZXh0LWFsaWduOiBsZWZ0OyBjb2xvcjpncmV5OyBmb250LXNpemU6MTAwJSA7JyksIGVzY2FwZSA9IEZBTFNFKSAlPiUKICAgICAgRFQ6OmZvcm1hdFN0eWxlKCBjb2x1bW5zID0gbmFtZXMoZnVzaW9ucy50YWJsZSksIGBmb250LXNpemVgID0gJzEycHgnLCAndGV4dC1hbGlnbicgPSAnY2VudGVyJyApCiAgCiAgZnVzaW9ucy5zdW1tYXJ5Cn0KCiMjIyMjIFNhdmUgdGhlIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICAKICAjIyMjIyBDcmVhdGUgZGlyZWN0b3J5IGZvciB0YWJsZXMKICBmdXNpb25zVGFibGVEaXIgPC0gcGFzdGUocmVzdWx0c19kaXIsICJmdXNpb25zVGFibGVzIiwgc2VwID0gIi8iKQogIGlmICggIWZpbGUuZXhpc3RzKGZ1c2lvbnNUYWJsZURpcikgKSB7CiAgICAgICAgICBkaXIuY3JlYXRlKGZ1c2lvbnNUYWJsZURpciwgcmVjdXJzaXZlPVRSVUUpCiAgfQoKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1mdXNpb25zLnN1bW1hcnksIGZpbGU9cGFzdGUoZnVzaW9uc1RhYmxlRGlyLCAiZnVzaW9ucy5zdW1tYXJ5Lmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZSBhbmQgcmV0dXJuIG91dHB1dApybShmdXNpb25zLnRhYmxlLCBmdXNpb25zLnN1bW1hcnkpCmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpDZWxscyBpbiA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBpbmRpY2F0ZSAqKkROQS1zdXBwb3J0ZWQqKiBmdXNpb24gZ2VuZXMgKHNlZSBbU3RydWN0dXJhbCB2YXJpYW50c10gc2VjdGlvbiksIGNlbGxzIGluIDxzcGFuIHN0eWxlPSJjb2xvcjojMDJkNjUzIj5HUkVFTjwvc3Bhbj4gaW5kaWNhdGUgZnVzaW9uIGV2ZW50cyAqKnJlcG9ydGVkIGluIFtGdXNpb25HREJdKGh0dHBzOi8vY2NzbS51dGguZWR1L0Z1c2lvbkdEQil7dGFyZ2V0PSJfYmxhbmsifSoqLCBhbmQgY2VsbHMgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOiM3Njc2ODkiPkdSRVk8L3NwYW4+IGluZGljYXRlIGZ1c2lvbnMgY29udGFpbmluZyAqKltDYW5jZXIgZ2VuZXNdKiouIEdlbmUgZnVzaW9ucyByZXBvcnRlZCBpbiBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn0gYXJlIGh5cGVybGlua2VkLiBHZW5lcyBrbm93biB0byBiZSBpbnZvbHZlZCBpbiBnZW5lIGZ1c2lvbnMgYXJlIGZsYWdnZWQgYmFzZWQgb24gaW5mb3JtYXRpb24gcHJvdmlkZWQgaW4gW0Z1c2lvbkdEQl0oaHR0cHM6Ly9jY3NtLnV0aC5lZHUvRnVzaW9uR0RCKXt0YXJnZXQ9Il9ibGFuayJ9IGFuZCBbQ2FuY2VyIEdlbm9tZSBJbnRlcnByZXRlcl0oaHR0cHM6Ly93d3cuY2FuY2VyZ2Vub21laW50ZXJwcmV0ZXIub3JnL2Jpb21hcmtlcnMpe3RhcmdldD0iX2JsYW5rIn0gKENHSSkgZGF0YWJhc2VzLiAqQnJlYWtwb2ludCAoQS9CKSogLSBnZW5vbWljIGNvb3JkaW5hdGVzIG9mIHRoZSBicmVha3BvaW50cyBpbiBnZW5lIEEvQjsgKlNpdGUgKEEvQikqIC0gbG9jYXRpb24gb2YgdGhlIGJyZWFrcG9pbnRzIGluIGdlbmUgQS9COyAqVHlwZSogLSB0eXBlIG9mIGV2ZW50IGJhc2VkIG9uIHRoZSBvcmllbnRhdGlvbiBvZiB0aGUgc3VwcG9ydGluZyByZWFkcyBhbmQgdGhlIGNvb3JkaW5hdGVzIG9mIGJyZWFrcG9pbnRzCgpGdXNpb24gZXZlbnRzIGFyZSBvcmRlcmVkIGJ5IHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCioqRE5BIHN1cHBvcnQgKEEvQikqKjogRE5BLXN1cHBvcnRlZCBmdXNpb24gZ2VuZShzKSAoc2VlIFtTdHJ1Y3R1cmFsIHZhcmlhbnRzXSBzZWN0aW9uKQoKKipDb25maWRlbmNlKiogbGV2ZWwgZnJvbSBbQXJyaWJhXShodHRwczovL2FycmliYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvKXt0YXJnZXQ9Il9ibGFuayJ9IHRvb2wKCioqUmVwb3J0ZWQgZnVzaW9uKio6IGZ1c2lvbiBldmVudCByZXBvcnRlZCBpbiBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn0KCioqU3BsaXQgY291bnQqKjogdGhlIG51bWJlciBvZiBzdXBwb3J0aW5nIHNwbGl0IHJlYWRzCgoqKlBhaXIgY291bnQqKjogdGhlIG51bWJlciBvZiBzdXBwb3J0aW5nIHBhaXIgcmVhZHMKCioqQ2FuY2VyIGdlbmUocykqKjogZ2VuZSBmdXNpb24gZXZlbnRzIGludm9sdmluZyBbQ2FuY2VyIGdlbmVzXQoKKipGdXNpb24gZ2VuZSAoQS9CKSoqOiBnZW5lKHMpIGtub3duIHRvIGJlIGludm9sdmVkIGluIHR1bW9yaWdlbmVzaXMgYWNyb3NzIGNhbmNlciB0eXBlcyBiYXNlZCBvbiBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn0gYW5kIFtDR0ldKGh0dHBzOi8vd3d3LmNhbmNlcmdlbm9tZWludGVycHJldGVyLm9yZy9iaW9tYXJrZXJzKXt0YXJnZXQ9Il9ibGFuayJ9IGRhdGFiYXNlcwoKPC9mb250Pgo8L2RldGFpbHM+CgoqKioKCiMjIyAtIEdlbm9taWMgdmlldwoKPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPioqYHIgaWYgKCBydW5GdXNpb25DaHVuayApIHsgbnJvdyhmdXNpb25zWyAoZnVzaW9ucyRnZW5lQV9kbmFfc3VwcG9ydCA9PSAiWWVzIiB8IGZ1c2lvbnMkZ2VuZUJfZG5hX3N1cHBvcnQgPT0gIlllcyIpICYgZnVzaW9ucyRjaXJjb3MgPT0gIlllcyIsIF0pIH0gZWxzZSB7IGMoIjAiKSB9YCoqPC9zcGFuPiAqKkROQS1zdXBwb3J0ZWQqKiBmdXNpb24gZ2VuZXMgKHNlZSBbU3RydWN0dXJhbCB2YXJpYW50c10gc2VjdGlvbikgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojMDJkNjUzIj4qKmByIGlmICggcnVuRnVzaW9uQ2h1bmsgKSB7IG5yb3coZnVzaW9uc1sgZnVzaW9ucyRyZXBvcnRlZF9mdXNpb24gPT0gIlllcyIgJiBmdXNpb25zJGNpcmNvcyA9PSAiWWVzIiwgXSkgfSBlbHNlIHsgYygiMCIpIH1gKio8L3NwYW4+IGZ1c2lvbnMgZXZlbnRzICoqcmVwb3J0ZWQgaW4gW0Z1c2lvbkdEQl0oaHR0cHM6Ly9jY3NtLnV0aC5lZHUvRnVzaW9uR0RCKXt0YXJnZXQ9Il9ibGFuayJ9KiogYXJlIHByZXNlbnRlZCBpbiB0aGUgZ2Vub21pYyBjb250ZXh0LiAqPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPlJlZDwvc3Bhbj4qIGNvbG91ciBpcyB1c2VkIGZvciBsaW5rcyBiZXR3ZWVuIHBvc2l0aW9ucyBvZiBzYW1lIGNocm9tb3NvbWVzIGFuZCAqPHNwYW4gc3R5bGU9ImNvbG9yOiMwMDAwZmYiPmJsdWU8L3NwYW4+KiBmb3IgbGlua3MgYmV0d2VlbiBkaWZmZXJlbnQgY2hyb21vc29tZXMuIFRoZSB0YWJsZSBhdCB0aGUgYm90dG9tIGNvbnRhaW5zIGdlbm9taWMgY29vcmRpbmdhdGVzIG9mIGluZGl2aWR1YWwgZnVzaW9uIGdlbmVzIHNvcnRlZCBiYXNlZCBvbiB0aGVpciBnZW5vbWljIGxvY2F0aW9uLgoKPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPk5PVEU8L3NwYW4+OiAqKmByIGlmICggcnVuRnVzaW9uQ2h1bmsgKSB7IG5yb3coZnVzaW9uc1sgKGZ1c2lvbnMkZ2VuZUFfZG5hX3N1cHBvcnQgPT0gIlllcyIgfCBmdXNpb25zJGdlbmVCX2RuYV9zdXBwb3J0ID09ICJZZXMiIHwgZnVzaW9ucyRnZW5lQV9kbmFfc3VwcG9ydCA9PSAiWWVzIikgJiBmdXNpb25zJGNpcmNvcyAhPSAiWWVzIiwgXSkgfSBlbHNlIHsgYygiMCIpIH1gKiogb2Ygc3VjaCBmdXNpb25zIGRvIG5vdCBoYXZlIGdlbm9taWMgaW5mb3JtYXRpb24gYXZhaWxhYmxlIGFuZCBhcmUgbm90IHByZXNlbnRlZCBvbiB0aGUgKmNpcmNvcyBwbG90KiAoc2VlICpHZW5vbWljIHZpZXcqIGNvbHVtbiBpbiB0aGUgWy0gU3VtbWFyeV0gdGFibGUpLiAKCmByIGlmICggIXJ1blNWc0NodW5rICkgeyBjKCJHZW5vbWljIGRhdGEgZm9yIHRoaXMgc2FtcGxlIGlzICoqTk9UIEFWQUlMQUJMRSoqLiIpIH1gCgpgYGB7ciBjaXJjb3NfcHJlcCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOCwgZXZhbCA9IHJ1bkZ1c2lvbkNodW5rfQojIyMjIyBLZWVwIG9ubHkgcmVwb3J0ZWQgZnVzaW9ucyBvciB0aG9zZSB3aXRoIG9yIGNhbmNlciBnZW5lKHMpIGludm9sdmVkCmlmICggcnVuU1ZzQ2h1bmsgKSB7CiAgZnVzaW9uX2Fubm90X3RvcCA8LSBmdXNpb25fYW5ub3RbIGZ1c2lvbl9hbm5vdCRyZXBvcnRlZF9mdXNpb24gPT0gIlllcyIgfCBmdXNpb25fYW5ub3QkZ2VuZUFfZG5hX3N1cHBvcnQgPT0gIlllcyIgfCBmdXNpb25fYW5ub3QkZ2VuZUJfZG5hX3N1cHBvcnQgPT0gIlllcyIgLCBdCn0gZWxzZSB7CiAgZnVzaW9uX2Fubm90X3RvcCA8LSBmdXNpb25fYW5ub3RbIGZ1c2lvbl9hbm5vdCRyZXBvcnRlZF9mdXNpb24gPT0gIlllcyIgLCBdCn0KCmlmICggbnJvdyhmdXNpb25fYW5ub3RfdG9wKSA+IDAgKSB7CiAgCiAgIyMjIyMgQ3JlYXRlIGZvbGRlciBmb3IgZnVzaW9uIHBsb3RzCiAgZnVzaW9uc1Bsb3REaXIgPC0gcGFzdGUocmVzdWx0c19kaXIsICJmdXNpb25zUGxvdCIsIHNlcCA9ICIvIikKICAgIAogIGlmICggIWZpbGUuZXhpc3RzKGZ1c2lvbnNQbG90RGlyKSApIHsKICAgIGRpci5jcmVhdGUoZnVzaW9uc1Bsb3REaXIsIHJlY3Vyc2l2ZT1UUlVFKQogIH0KICAKICAjIyMjIyBQcmVwYXJlIG9iamVjdCBmb3IgUkNpcmNvcwogIGV2YWwocGFyc2UoIHRleHQ9cGFzdGUwKCJkYXRhKFVDU0MuSEciLCBwYXJhbXMkdWNzY19nZW5vbWVfYXNzZW1ibHksICIuSHVtYW4uQ3l0b0JhbmRJZGVvZ3JhbSkiKSkpCiAgY3l0by5pbmZvIDwtIGV2YWwocGFyc2UoIHRleHQ9cGFzdGUwKCJVQ1NDLkhHIiwgcGFyYW1zJHVjc2NfZ2Vub21lX2Fzc2VtYmx5LCAiLkh1bWFuLkN5dG9CYW5kSWRlb2dyYW0iKSkpCiAgICAKICAjIyMjIyBDaGVjayBpZiBhbGwgZHJpdmVyIGdlbmVzIGFyZSBsb2NhdGVkIGluIHN0YW5kYXJkIGNocm9tb3NvbWVzCiAgZnVzaW9uX2Fubm90X3RvcCA8LSBmdXNpb25fYW5ub3RfdG9wWyBwYXN0ZTAoImNociIsIGZ1c2lvbl9hbm5vdF90b3AkU0VRTkFNRSkgJWluJSBjeXRvLmluZm8kQ2hyb21vc29tZSwgIF0KICAKICBmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycyA8LSBmdXNpb25fYW5ub3RfdG9wWywgYygiU0VRTkFNRSIsICJHRU5FU0VRU1RBUlQiLCAiR0VORVNFUUVORCIsICJTWU1CT0wiLCJTRVFOQU1FLjEiLCAiR0VORVNFUVNUQVJULjEiLCAiR0VORVNFUUVORC4xIiwgIlNZTUJPTC4xIildCiAgCiAgIyMjIyMgQWRkICJjaHIiIHRvIGNocm9tb3NvbWUgbnVtYmVycwogIGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzJFNFUU5BTUUgPC0gcGFzdGUwKCJjaHIiLCBmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycyRTRVFOQU1FKQogIGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzJFNFUU5BTUUuMSA8LSBwYXN0ZTAoImNociIsIGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzJFNFUU5BTUUuMSkKICAKICAjIyMjIyBDaGFuZ2UgY29sdW1uIG5hbWVzCiAgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpIDwtIGdzdWIoIlNFUU5BTUUiLCAiQ2hyb21vc29tZSIsIG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzKSkKICBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykgPC0gZ3N1YigiR0VORVNFUVNUQVJUIiwgImNocm9tU3RhcnQiLCBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykpCiAgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpIDwtIGdzdWIoIkdFTkVTRVFFTkQiLCAiY2hyb21FbmQiLCBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykpCiAgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpIDwtIGdzdWIoIlNZTUJPTCIsICJHZW5lIiwgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpKQogIG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzKSA8LSBnc3ViKCJDaHJvbW9zb21lLjEiLCAiQ2hyb21vc29tZSIsIG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzKSkKICBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykgPC0gZ3N1YigiY2hyb21TdGFydC4xIiwgImNocm9tU3RhcnQiLCBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykpCiAgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpIDwtIGdzdWIoImNocm9tRW5kLjEiLCAiY2hyb21FbmQiLCBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykpCiAgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpIDwtIGdzdWIoIkdlbmUuMSIsICJHZW5lIiwgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpKQogIAogICMjIyMjIFJlbW92ZSBlbnRyaWVzIHdpdGggbWlzc2luZyBnZW5vbWljIGNvb3JkaW5hdGVzCiAgZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMgPC0gZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnNbY29tcGxldGUuY2FzZXMoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpLCBdCiAgZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MgPC0gcmJpbmQoZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnNbLCAxOjQgXSwgZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnNbLCA1OjggXSkKICBmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycyA8LSBmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlyc1ssIGNvbG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzKSAlIWluJSBjKCJHZW5lIiwgIkdlbmUuMSIpIF0KICAKICAjIyMjIyBHZW5lcmF0ZSBjaXJjb3MgcGxvdAogIFJDaXJjb3MuU2V0LkNvcmUuQ29tcG9uZW50cyggY3l0by5pbmZvPWN5dG8uaW5mbywgY2hyLmV4Y2x1ZGU9TlVMTCwgdHJhY2tzLmluc2lkZT00LCB0cmFja3Mub3V0c2lkZT0wICkKICBSQ2lyY29zLlNldC5QbG90LkFyZWEoKSAgCiAgUkNpcmNvcy5DaHJvbW9zb21lLklkZW9ncmFtLlBsb3QoKQogIFJDaXJjb3MuR2VuZS5Db25uZWN0b3IuUGxvdChnZW5vbWljLmRhdGEgPSBmdXNpb25fYW5ub3RfdG9wLmNpcmNvcywgdHJhY2subnVtID0gMSwgc2lkZT0iaW4iKSAKICBSQ2lyY29zLkdlbmUuTmFtZS5QbG90KGdlbmUuZGF0YSA9IGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLCBuYW1lLmNvbCA9IDQsIHRyYWNrLm51bSA9IDIsIHNpZGUgPSAiaW4iKQogIFJDaXJjb3MuTGluay5QbG90KGxpbmsuZGF0YSA9IGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzLCB0cmFjay5udW09NCwgYnkuY2hyb21vc29tZT1UUlVFLCBpcy5zb3J0ZWQ9RkFMU0UsIGxpbmVXaWR0aD1yZXAoMiwgbnJvdyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykpKQp9CmBgYAoKYGBge3IgZnVzaW9uc19nZW5vbWljX3ZpZXdfY2lyY29zX3NhdmUsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIGV2YWwgPSBydW5GdXNpb25DaHVua30KIyMjIyMgR2VuZXJhdGUgY2lyY29zIHBsb3QgcmVwcmVzZW50aW5nIGdlbmUgZnVzaW9uIGV2ZW50cy4gTk9URS4gT25seSBmdXNpb25zIGludm9sdmluZyBmdXNpb24gZ2VuZXMgc3VwcG9ydGVkIGJ5IE1BTlRBIG9yIHJlcG9ydGVkIGZ1c2lvbnMgYXJlIHByZXNlbnRlZAppZiAoIG5yb3coZnVzaW9uX2Fubm90X3RvcCkgPiAwICkgewogIAogICMjIyMjIFNhdmUgY2lyY29zIGludG8gYSBwbmcgZmlsZQogIHBuZyggZmlsZW5hbWUgPSBwYXN0ZShmdXNpb25zUGxvdERpciwgImNpcmNvc1Bsb3QucG5nIiwgc2VwPSIvIiksIHdpZHRoID0gODAwLCBoZWlnaHQgPSA4MDAsIHVuaXRzID0gInB4IiwgcG9pbnRzaXplID0gMjQgKQogIFJDaXJjb3MuU2V0LkNvcmUuQ29tcG9uZW50cyggY3l0by5pbmZvPWN5dG8uaW5mbywgY2hyLmV4Y2x1ZGU9TlVMTCwgdHJhY2tzLmluc2lkZT00LCB0cmFja3Mub3V0c2lkZT0wICkKICBSQ2lyY29zLlNldC5QbG90LkFyZWEoKSAgCiAgUkNpcmNvcy5DaHJvbW9zb21lLklkZW9ncmFtLlBsb3QoKQogIFJDaXJjb3MuR2VuZS5Db25uZWN0b3IuUGxvdChnZW5vbWljLmRhdGEgPSBmdXNpb25fYW5ub3RfdG9wLmNpcmNvcywgdHJhY2subnVtID0gMSwgc2lkZT0iaW4iKSAKICBSQ2lyY29zLkdlbmUuTmFtZS5QbG90KGdlbmUuZGF0YSA9IGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLCBuYW1lLmNvbCA9IDQsIHRyYWNrLm51bSA9IDIsIHNpZGUgPSAiaW4iKQogIFJDaXJjb3MuTGluay5QbG90KGxpbmsuZGF0YSA9IGZ1c2lvbl9hbm5vdF90b3AuY2lyY29zLnBhaXJzLCB0cmFjay5udW09NCwgYnkuY2hyb21vc29tZT1UUlVFLCBpcy5zb3J0ZWQ9RkFMU0UsIGxpbmVXaWR0aD1yZXAoMiwgbnJvdyhmdXNpb25fYW5ub3RfdG9wLmNpcmNvcy5wYWlycykpKQogIGludmlzaWJsZShkZXYub2ZmKCkpCiAgICAKICAjIyMjIyBDbGVhbiB0aGUgc3BhY2UKICBybShmdXNpb25fYW5ub3RfdG9wLmNpcmNvcywgZnVzaW9uX2Fubm90X3RvcC5jaXJjb3MucGFpcnMpCiAgCiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCiAgCn0gZWxzZSB7CiAgY2F0KCJOb25lIG9mIHRoZSB0cmFuc2NyaXB0b21lLWJhc2VkIGZ1c2lvbiBldmVudHMgaGF2ZSBzdXBwb3J0aW5nIGV2aWRlbmNlIGZyb20gRE5BIGRhdGEgb3Igd2FzIHByZXZpb3VzbHkgcmVwb3J0ZWQuIikKfQpgYGAKCmBgYHtyIGdlbm9taWNfdmlld19jaXJjb3NfdGFibGVfZnVzaW9ucywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuRnVzaW9uQ2h1bmt9CmlmICggbnJvdyhmdXNpb25fYW5ub3RfdG9wKSA+IDAgKSB7CiAgCiAgIyMjIyMgQ2xlYW4gdGhlIHRhYmxlIGZvciBiZXR0ZXIgcHJlc2VudGF0aW9uCiAgIyMjIyMgRHJhZ2VuICsgQXJyaWJhIC8gUGl6emx5ICsgQXJyaWJhCiAgaWYgKCBydW5EcmFnZW5GdXNpb25DaHVuayAmJiBydW5BcnJpYmFDaHVuayApIHsKICAgIGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4gPC0gZnVzaW9uX2Fubm90X3RvcFssIGMoIlNZTUJPTCIsICJTRVFOQU1FIiwgIkdFTkVTRVFTVEFSVCIsICJHRU5FU0VRRU5EIiwgIlNZTUJPTC4xIiwgIlNFUU5BTUUuMSIsICJHRU5FU0VRU1RBUlQuMSIsICJHRU5FU0VRRU5ELjEiLCAiYnJlYWtwb2ludEEiLCAiYnJlYWtwb2ludEIiLCAic3BsaXRfcmVhZHMiLCAic3BsaXRfcmVhZHNBIiwgInNwbGl0X3JlYWRzQiIsICJkaXNjb3JkYW50X21hdGVzIiwgImdlbmVBX2RuYV9zdXBwb3J0IiwgImdlbmVCX2RuYV9zdXBwb3J0IiwgInJlcG9ydGVkX2Z1c2lvbiIsICJmdXNpb25zX2NhbmNlciIpIF0KICAgIAogICMjIyMjIERyYWdlbiBvbmx5CiAgfSBlbHNlIGlmICggcnVuRHJhZ2VuRnVzaW9uQ2h1bmsgKSB7CiAgICAKICAgICMjIyMjICBEcmFnZW4ncyBmdXNpb24gZm9ybWF0IHZlcnNpb24gMy45LjMKICAgIGlmICggYWxsKGMoIkdlbmVBTG9jYXRpb24iLCAiR2VuZUJMb2NhdGlvbiIsICJOdW1TcGxpdFJlYWRzIiwiTnVtU29mdENsaXBwZWRSZWFkcyIsICJTY29yZSIpICVpbiUgY29sbmFtZXMoZHJhZ2VuLmZ1c2lvbnMpKSApIHsKICAgICAgZnVzaW9uX2Fubm90X3RvcC5jbGVhbiA8LSBmdXNpb25fYW5ub3RfdG9wWywgYygiU1lNQk9MIiwgIlNFUU5BTUUiLCAiR0VORVNFUVNUQVJUIiwgIkdFTkVTRVFFTkQiLCAiU1lNQk9MLjEiLCAiU0VRTkFNRS4xIiwgIkdFTkVTRVFTVEFSVC4xIiwgIkdFTkVTRVFFTkQuMSIsICJicmVha3BvaW50QSIsICJicmVha3BvaW50QiIsICJzcGxpdF9yZWFkcyIsICJnZW5lQV9kbmFfc3VwcG9ydCIsICJnZW5lQl9kbmFfc3VwcG9ydCIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiKSBdCiAgICAgIAogICAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgcHJpb3IgdG8gdmVyc2lvbiAzLjkuMwogICAgfSBlbHNlIHsKICAgICAgZnVzaW9uX2Fubm90X3RvcC5jbGVhbiA8LSBmdXNpb25fYW5ub3RfdG9wWywgYygiU1lNQk9MIiwgIlNFUU5BTUUiLCAiR0VORVNFUVNUQVJUIiwgIkdFTkVTRVFFTkQiLCAiU1lNQk9MLjEiLCAiU0VRTkFNRS4xIiwgIkdFTkVTRVFTVEFSVC4xIiwgIkdFTkVTRVFFTkQuMSIsICJicmVha3BvaW50QSIsICJicmVha3BvaW50QiIsICJnZW5lQV9kbmFfc3VwcG9ydCIsICJnZW5lQl9kbmFfc3VwcG9ydCIsICJyZXBvcnRlZF9mdXNpb24iLCAiZnVzaW9uc19jYW5jZXIiKSBdCiAgICB9CiAgICAKICAjIyMjIyBQaXp6bHkgb25seQogIH0gZWxzZSB7CiAgICBmdXNpb25fYW5ub3RfdG9wLmNsZWFuIDwtIGZ1c2lvbl9hbm5vdF90b3BbLCBjKCJTWU1CT0wiLCAiU0VRTkFNRSIsICJHRU5FU0VRU1RBUlQiLCAiR0VORVNFUUVORCIsICJTWU1CT0wuMSIsICJTRVFOQU1FLjEiLCAiR0VORVNFUVNUQVJULjEiLCAiR0VORVNFUUVORC4xIiwgInNwbGl0X3JlYWRzIiwgImRpc2NvcmRhbnRfbWF0ZXMiLCAiZ2VuZUFfZG5hX3N1cHBvcnQiLCAiZ2VuZUJfZG5hX3N1cHBvcnQiLCAicmVwb3J0ZWRfZnVzaW9uIiwgImZ1c2lvbnNfY2FuY2VyIikgXQogIH0KICAKICAjIyMjIyBPcmRlciBmdXNpb25zIGJhc2VkIG9uIHRoZSBnZW5vbWljIGxvY2F0aW9uIChjaHJvbSBhbmQgc3RhcnQgcG9zaXRpb25zKQogIGNock9yZGVyIDwtYygoMToyMiksIlgiLCJZIiwiTSIpCiAgCiAgZnVzaW9uX2Fubm90X3RvcC5jbGVhbiRTRVFOQU1FIDwtIGZhY3RvcihmdXNpb25fYW5ub3RfdG9wLmNsZWFuJFNFUU5BTUUsIGNock9yZGVyLCBvcmRlcmVkPVRSVUUpCiAgZnVzaW9uX2Fubm90X3RvcC5jbGVhbiRTRVFOQU1FLjEgPC0gZmFjdG9yKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4kU0VRTkFNRS4xLCBjaHJPcmRlciwgb3JkZXJlZD1UUlVFKQogIGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4gPC0gZnVzaW9uX2Fubm90X3RvcC5jbGVhbltkby5jYWxsKG9yZGVyLCBmdXNpb25fYW5ub3RfdG9wLmNsZWFuWywgYygiU0VRTkFNRSIsICJTRVFOQU1FLjEiLCAiR0VORVNFUVNUQVJUIiwgIkdFTkVTRVFTVEFSVC4xIildKSwgXQogIAogICMjIyMjIERyYWdlbiArIEFycmliYSAvIFBpenpseSArIEFycmliYQogIGlmICggcnVuRHJhZ2VuRnVzaW9uQ2h1bmsgJiYgcnVuQXJyaWJhQ2h1bmspIHsKICAgIG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pIDwtIGMoIkdlbmUgQSIsICJDaHJvbSAoQSkiLCAiU3RhcnQgKEEpIiwgIkVuZCAoQSkiLCAiR2VuZSBCIiwgIkNocm9tIChCKSIsICJTdGFydCAoQikiLCAiRW5kIChCKSIsICJCcmVha3BvaW50IChBKSIsICJCcmVha3BvaW50IChCKSIsICJTcGxpdCByZWFkcyAoVG90YWwpIiwgIlNwbGl0IHJlYWRzIChBKSIsICJTcGxpdCByZWFkcyAoQikiLCAiUGFpciByZWFkcyIsICJETkEgc3VwcG9ydCAoQSkiLCAiRE5BIHN1cHBvcnQgKEIpIiwgIlJlcG9ydGVkIGZ1c2lvbiIsICJDYW5jZXIgZ2VuZShzKSIpCiAgICAKICAjIyMjIyBEcmFnZW4gb25seQogIH0gZWxzZSBpZiAoIHJ1bkRyYWdlbkZ1c2lvbkNodW5rICkgewogICAgCiAgICAjIyMjIyAgRHJhZ2VuJ3MgZnVzaW9uIGZvcm1hdCB2ZXJzaW9uIDMuOS4zCiAgICBpZiAoIGFsbChjKCJHZW5lQUxvY2F0aW9uIiwgIkdlbmVCTG9jYXRpb24iLCAiTnVtU3BsaXRSZWFkcyIsIk51bVNvZnRDbGlwcGVkUmVhZHMiLCAiU2NvcmUiKSAlaW4lIGNvbG5hbWVzKGRyYWdlbi5mdXNpb25zKSkgKSB7CiAgICAgIG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pIDwtIGMoIkdlbmUgQSIsICJDaHJvbSAoQSkiLCAiU3RhcnQgKEEpIiwgIkVuZCAoQSkiLCAiR2VuZSBCIiwgIkNocm9tIChCKSIsICJTdGFydCAoQikiLCAiRW5kIChCKSIsICJCcmVha3BvaW50IChBKSIsICJCcmVha3BvaW50IChCKSIsICJTcGxpdCByZWFkcyIsICJETkEgc3VwcG9ydCAoQSkiLCAiRE5BIHN1cHBvcnQgKEIpIiwgIlJlcG9ydGVkIGZ1c2lvbiIsICJDYW5jZXIgZ2VuZShzKSIpCiAgICAgIAogICAgIyMjIyMgIERyYWdlbidzIGZ1c2lvbiBmb3JtYXQgcHJpb3IgdG8gdmVyc2lvbiAzLjkuMwogICAgfSBlbHNlIHsKICAgICAgbmFtZXMoZnVzaW9uX2Fubm90X3RvcC5jbGVhbikgPC0gYygiR2VuZSBBIiwgIkNocm9tIChBKSIsICJTdGFydCAoQSkiLCAiRW5kIChBKSIsICJHZW5lIEIiLCAiQ2hyb20gKEIpIiwgIlN0YXJ0IChCKSIsICJFbmQgKEIpIiwgIkJyZWFrcG9pbnQgKEEpIiwgIkJyZWFrcG9pbnQgKEIpIiwgIkROQSBzdXBwb3J0IChBKSIsICJETkEgc3VwcG9ydCAoQikiLCAiUmVwb3J0ZWQgZnVzaW9uIiwgIkNhbmNlciBnZW5lKHMpIikKICAgIH0KICAgIAogICMjIyMjIFBpenpseSBvbmx5CiAgfSBlbHNlIHsKICAgIG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pIDwtIGMoIkdlbmUgQSIsICJDaHJvbSAoQSkiLCAiU3RhcnQgKEEpIiwgIkVuZCAoQSkiLCAiR2VuZSBCIiwgIkNocm9tIChCKSIsICJTdGFydCAoQikiLCAiRW5kIChCKSIsICJTcGxpdCByZWFkcyIsICJQYWlyIHJlYWRzIiwgIkROQSBzdXBwb3J0IChBKSIsICJETkEgc3VwcG9ydCAoQikiLCAiUmVwb3J0ZWQgZnVzaW9uIiwgIkNhbmNlciBnZW5lKHMpIikKICB9CgogIGZ1c2lvbnMuZ2Vub21pY1ZpZXcgPC0gRFQ6OmRhdGF0YWJsZSggZGF0YSA9IGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4sIGZpbHRlcj0ibm9uZSIsIHJvd25hbWVzID0gRkFMU0UsIGV4dGVuc2lvbnMgPSBjKCdCdXR0b25zJywnU2Nyb2xsZXInKSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDEwLCBkb20gPSAnQmZydGlwJywgYnV0dG9ucyA9IGMoJ2V4Y2VsJywgJ2NzdicsICdwZGYnLCdjb3B5JywnY29sdmlzJyksIHNjcm9sbFggPSBUUlVFLCBkZWZlclJlbmRlciA9IFRSVUUsIHNjcm9sbFkgPSAiMTY3cHgiLCBzY3JvbGxlciA9IFRSVUUpLCB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gMzE4LCAgZXNjYXBlID0gRkFMU0UpICU+JQogICAgICBEVDo6Zm9ybWF0U3R5bGUoIGNvbHVtbnMgPSBuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNsZWFuKSwgYGZvbnQtc2l6ZWAgPSAnMTJweCcsICd0ZXh0LWFsaWduJyA9ICdjZW50ZXInICkgJT4lCiAgICAKICAgICAgIyMjIyMgSGlnaGxpZ2h0IHJvd3Mgd2l0aCBmdXNpb25zIGludm9sdmluZyBjYW5jZXIgZ2VuZXMgKGdyZXkpCiAgICAgIERUOjpmb3JtYXRTdHlsZSggY29sdW1ucyA9IGNvbG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pICVpbiUgIkNhbmNlciBnZW5lKHMpIiwgYmFja2dyb3VuZENvbG9yID0gRFQ6OnN0eWxlRXF1YWwoYygiLSIsICJZZXMiKSwgYygndHJhbnNwYXJlbnQnLCAnbGlnaHRncmV5JykpICkgJT4lCiAgICAgIERUOjpmb3JtYXRTdHlsZSggY29sdW1ucyA9IGNvbG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pICVpbiUgIkROQSBzdXBwb3J0IChBKSIsIGJhY2tncm91bmRDb2xvciA9IERUOjpzdHlsZUVxdWFsKGMoIi0iLCAiWWVzIiksIGMoJ3RyYW5zcGFyZW50JywgJ2NvcmFsJykpICkgJT4lCiAgICAgIERUOjpmb3JtYXRTdHlsZSggY29sdW1ucyA9IGNvbG5hbWVzKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pICVpbiUgIkROQSBzdXBwb3J0IChCKSIsIGJhY2tncm91bmRDb2xvciA9IERUOjpzdHlsZUVxdWFsKGMoIi0iLCAiWWVzIiksIGMoJ3RyYW5zcGFyZW50JywgJ2NvcmFsJykpICkgJT4lCiAgICBEVDo6Zm9ybWF0U3R5bGUoIGNvbHVtbnMgPSBjb2xuYW1lcyhmdXNpb25fYW5ub3RfdG9wLmNsZWFuKSAlaW4lICJSZXBvcnRlZCBmdXNpb24iLCBiYWNrZ3JvdW5kQ29sb3IgPSBEVDo6c3R5bGVFcXVhbCggYygiLSIsICJZZXMiKSwgYygndHJhbnNwYXJlbnQnLCAnbGlnaHRncmVlbicpKSApCgogIGZ1c2lvbnMuZ2Vub21pY1ZpZXcKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGZ1c2lvbl9hbm5vdF90b3AuY2xlYW4pCmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpDZWxscyBpbiA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBpbmRpY2F0ZSAqKkROQS1zdXBwb3J0ZWQqKiBmdXNpb24gZ2VuZXMgKHNlZSBbU3RydWN0dXJhbCB2YXJpYW50c10gc2VjdGlvbiksIGNlbGxzIGluIDxzcGFuIHN0eWxlPSJjb2xvcjojMDJkNjUzIj5HUkVFTjwvc3Bhbj4gaW5kaWNhdGUgZ2VuZSBmdXNpb25zICoqcmVwb3J0ZWQgaW4gW0Z1c2lvbkdEQl0oaHR0cHM6Ly9jY3NtLnV0aC5lZHUvRnVzaW9uR0RCKXt0YXJnZXQ9Il9ibGFuayJ9KiosIGFuZCBjZWxscyBoaWhnbGlnaHRlZCBpbiA8c3BhbiBzdHlsZT0iY29sb3I6Izc2NzY4OSI+R1JFWTwvc3Bhbj4gaW5kaWNhdGUgZnVzaW9ucyBjb250YWluaW5nICoqW0NhbmNlciBnZW5lc10qKi4gR2VuZXMga25vd24gdG8gYmUgaW52b2x2ZWQgaW4gZ2VuZSBmdXNpb25zIGFyZSBmbGFnZ2VkIGJhc2VkIG9uIGluZm9ybWF0aW9uIHByb3ZpZGVkIGluIFtGdXNpb25HREJdKGh0dHBzOi8vY2NzbS51dGguZWR1L0Z1c2lvbkdEQil7dGFyZ2V0PSJfYmxhbmsifSBhbmQgW0NhbmNlciBHZW5vbWUgSW50ZXJwcmV0ZXJdKGh0dHBzOi8vd3d3LmNhbmNlcmdlbm9tZWludGVycHJldGVyLm9yZy9iaW9tYXJrZXJzKXt0YXJnZXQ9Il9ibGFuayJ9IChDR0kpIGRhdGFiYXNlcy4gRnVzaW9uIGV2ZW50cyBhcmUgb3JkZXJlZCBieSAqKmdlbm9taWMgY29vcmRpbmF0ZXMqKiBvZiAqKkdlbmUgQSoqIGFuZCB0aGVuICoqR2VuZSBCKiouICpETkEgc3VwcG9ydCAoZ2VuZSBBL0IpKiAtIEROQS1zdXBwb3J0ZWQgZnVzaW9uIGdlbmUocykgKHNlZSkgW1N0cnVjdHVyYWwgdmFyaWFudHNdIHNlY3Rpb24pOyAqUmVwb3J0ZWQgZnVzaW9uKiAtIGZ1c2lvbiBldmVudCByZXBvcnRlZCBpbiBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn07ICpDYW5jZXIgZ2VuZShzKSogLSBnZW5lIGZ1c2lvbiBldmVudHMgaW52b2x2aW5nIFtDYW5jZXIgZ2VuZXNdCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCmBgYHtyIGdlbm9taWNfdmlld190YWJsZV9mdXNpb25zX3NhdmUsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1bkZ1c2lvbkNodW5rfQojIyMjIyBTYXZlIHRoZSB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBucm93KGZ1c2lvbl9hbm5vdF90b3ApID4gMCAmJiBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9ZnVzaW9ucy5nZW5vbWljVmlldywgZmlsZT1wYXN0ZShmdXNpb25zVGFibGVEaXIsICJmdXNpb25zLmdlbm9taWNWaWV3Lmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpICAKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CnJtKGZ1c2lvbnMuZ2Vub21pY1ZpZXcpCmBgYAoKKioqCgojIyMgLSBUb3AgaGl0cyB7LnRhYnNldH0KCkV4cHJlc3Npb24gcHJvZmlsZXMgZm9yIGdlbmUgZnVzaW9uIGV2ZW50cyBpbnZvbHZpbmcgKipETkEtc3VwcG9ydGVkIGZ1c2lvbioqIGdlbmVzIChzZWUgW1N0cnVjdHVyYWwgdmFyaWFudHNdIHNlY3Rpb24pLCBnZW5lIGZ1c2lvbnMgKipyZXBvcnRlZCBpbiBbRnVzaW9uR0RCXShodHRwczovL2Njc20udXRoLmVkdS9GdXNpb25HREIpe3RhcmdldD0iX2JsYW5rIn0qKiBvciAqKltDYW5jZXIgZ2VuZXNdKiosIGluZGljYXRlZCBpbiA8c3BhbiBzdHlsZT0iY29sb3I6IzAyZDY1MyI+Z3JlZW48L3NwYW4+LCA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+cmVkPC9zcGFuPiBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiM3Njc2ODkiPmdyZXk8L3NwYW4+IGNvbHVtbnMgaW4gdGhlIFtGdXNpb24gZ2VuZXNdIHRhYmxlLCByZXNwZWN0aXZlbHksIGFuZCB3aXRoIHRoZSBoaWdoZXN0ICpTcGxpdCBjb3VudCogYW5kICpQYWlyIGNvdW50KiB2YWx1ZXMuIAoKPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPk5PVEU8L3NwYW4+OiB0aGUgKnZpc3VhbGlzYXRpb24qIGlzIGF2YWlsYWJsZSBvbmx5IGZvciBmdXNpb24gZ2VuZXMgZGV0ZWN0ZWQgYnkgW0FycmliYV0oaHR0cHM6Ly9hcnJpYmEucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0Lyl7dGFyZ2V0PSJfYmxhbmsifSAoc2VlIHRoZSBbLSBTdW1tYXJ5XSB0YWJsZSkuCgpgYGB7ciB0b3BfaGl0c19mdXNpb25zLCBlY2hvPUZBTFNFLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQojIyMjIyBQcm92aWRlIGRldGFpbGVkIGV4cHJlc3Npb24gaW5mbyBmb3IgdGhlIHRvcCByYW5rZWQgZnVzaW9uIGV2ZW50cwp0YXJnZXRzIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dCmdlbmVzIDwtIGFzLmNoYXJhY3RlcihmdXNpb25zJGdlbmVBKQoKIyMjIyMgQ3JlYXRlIGxpc3RzIHRvIHN0b3JlCm91dHB1dF9jZGZfQSA8LSBsaXN0KCkKb3V0cHV0X2NkZl9CIDwtIGxpc3QoKQpvdXRwdXRfY291bnRzX0EgPC0gbGlzdCgpCm91dHB1dF9jb3VudHNfQiA8LSBsaXN0KCkKb3V0cHV0X2RlbnNpdHlfQSA8LSBsaXN0KCkKb3V0cHV0X2RlbnNpdHlfQiA8LSBsaXN0KCkKb3V0cHV0X3RhYmxlX1ogPC0gbGlzdCgpCm91dHB1dF90YWJsZV9wZXJjIDwtIGxpc3QoKQoKIyMjIyMgRGVhbCB3aXRoIG5vIGdlbmVzIG9yIHdoZW4gbW9yZSB0aGFuIDEwIGdlbmVzIGFyZSBvZiBpbnRlcmVzdAppZiAoIGxlbmd0aChnZW5lcykgPT0gMCApIHsKICBnZW5lcyA8LSBOVUxMCiAgZ2VuZXNfbm8gPC0gMAp9IGVsc2UgaWYgKCBsZW5ndGgoZ2VuZXMpID4gcGFyYW1zJHRvcF9nZW5lcyApIHsKICBnZW5lc19ubyA8LSBwYXJhbXMkdG9wX2dlbmVzCn0gZWxzZSB7CiAgZ2VuZXNfbm8gPC0gbGVuZ3RoKGdlbmVzKQp9CgojIyMjIyBBZGQgZ2VuZXMgQiB0byB0aGUgZnVzaW9ucyBnZW5lIGxpc3QKZ2VuZXMgPC0gYyhnZW5lcywgYXMuY2hhcmFjdGVyKGZ1c2lvbnMkZ2VuZUIpKQoKIyMjIyMgQ29sbGVjdCBpbmZvIGFuZCBwbG90cyBmb3IgZWFjaCBvZiB0aGUgdG9wIGZ1c2lvbnMKZm9yKCBpIGluIDE6Z2VuZXNfbm8gKSB7CiAgaWYgKCBnZW5lc19ubyA+IDAgKSB7CiAgICBnZW5lQSA8LSBhcy52ZWN0b3IoZnVzaW9ucyRnZW5lQVtpXSkKICAgIGdlbmVCIDwtIGFzLnZlY3RvcihmdXNpb25zJGdlbmVCW2ldKQogICAgCiAgICAjIyMjIyBGb3IgZWFjaCBnZW5lIGdlbmVyYXRlICgxKSBDREYgcGxvdCBhbmQgYWRkIGJveHBsb3QgYmVsb3cgdG8gc2hvdyB0aGUgZGF0YSB2YXJpYW5jZSBmb3Igc2VsZWN0ZWQgZ2VuZSBpbiBpbmRpdmlkdWFsIGdyb3VwcywgKDIpIGJhci1wbG90IG9mIHJlYWQgY291bnQgZGF0YSBhY3Jvc3MgYWxsIHNhbXBsZXMgYW5kICgzKSBkZW5zaXR5IHBsb3QgdG8gZGVtb25zdHJhdGUgZXhwcmVzc2lvbiBkaXN0cmlidXRpb24gaW4gaW52ZXN0aWdhdGVkIHNhbXBsZSAKICAgIGlmICggZ2VuZUEgJWluJSByb3duYW1lcyhkYXRhKSApIHsKICAgICAgCiAgICAgICMjIyMjIENERiBwbG90CiAgICAgIG91dHB1dF9jZGZfQVtbaV1dIDwtIGNkZlBsb3QoZ2VuZSA9IGdlbmVBLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgYWRkQm94UGxvdCA9IEZBTFNFLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQogICAgICAKICAgICAgIyMjIyMgQmFyLXBsb3Qgb2YgcmVhZCBjb3VudHMKICAgICAgIyMjIyMgRmlyc3QgbWFwIHRoZSBnZW5lIHN5bWJvbCB0byBFbnNtZWJsIElEICh1c2VkIGluIHRoZSBjb3VudHMgZGF0YSkKICAgICAgZ2VuZXMuRU5TRU1CTCA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRFTlNFTUJMWyByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRTWU1CT0wgPT0gIGdlbmVBIF0KICAgICAgCiAgICAgIG91dHB1dF9jb3VudHNfQVtbaV1dIDwtIGJhclBsb3QoZ2VuZSA9IGdlbmVzLkVOU0VNQkwsIGRhdGEgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dLCB5X3RpdGxlID0gIkNvdW50cyIsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAgKQogICAgICAKICAgICAgIyMjIyMgRGVuc2l0eSBwbG90IC0gZXhwcmVzc2lvbiBkaXN0cmlidXRpb24KICAgICAgb3V0cHV0X2RlbnNpdHlfQVtbaV1dIDwtIGRlbnNpdHlQbG90KGdlbmUgPSBnZW5lQSwgZGF0YSA9IGRhdGEsIG1haW5fdGl0bGU9ICIiLCB4X3RpdGxlID0gIlotc2NvcmUiLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGRpc3RyaWJ1dGlvbnMgPSBjKCJub3JtYWwiLCAiYmltb2RhbCIpLCBzY2FsaW5nID0gc2NhbGluZykKICAgIH0KICAgIAogICAgIyMjIyMgR2VuZSBCCiAgICBpZiAoIGdlbmVCICVpbiUgcm93bmFtZXMoZGF0YSkgJiYgZ2VuZUIgIT0gZ2VuZUEpIHsKICAgICAgCiAgICAgICMjIyMjIENERiBwbG90CiAgICAgIG91dHB1dF9jZGZfQltbaV1dIDwtIGNkZlBsb3QoZ2VuZSA9IGdlbmVCLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgYWRkQm94UGxvdCA9IEZBTFNFLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQogICAgICAKICAgICAgIyMjIyMgQmFyLXBsb3Qgb2YgcmVhZCBjb3VudHMKICAgICAgIyMjIyMgRmlyc3QgbWFwIHRoZSBnZW5lIHN5bWJvbCB0byBFbnNtZWJsIElEICh1c2VkIGluIHRoZSBjb3VudHMgZGF0YSkKICAgICAgZ2VuZXMuRU5TRU1CTCA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRFTlNFTUJMWyByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRTWU1CT0wgPT0gIGdlbmVCIF0KICAgICAgCiAgICAgIG91dHB1dF9jb3VudHNfQltbaV1dIDwtIGJhclBsb3QoZ2VuZSA9IGdlbmVzLkVOU0VNQkwsIGRhdGEgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dLCB5X3RpdGxlID0gIkNvdW50cyIsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAgKQogICAgICAKICAgICAgIyMjIyMgRGVuc2l0eSBwbG90IC0gZXhwcmVzc2lvbiBkaXN0cmlidXRpb24KICAgICAgb3V0cHV0X2RlbnNpdHlfQltbaV1dIDwtIGRlbnNpdHlQbG90KGdlbmUgPSBnZW5lQiwgZGF0YSA9IGRhdGEsIG1haW5fdGl0bGU9ICIiLCB4X3RpdGxlID0gIlotc2NvcmUiLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGRpc3RyaWJ1dGlvbnMgPSBjKCJub3JtYWwiLCAiYmltb2RhbCIpLCBzY2FsaW5nID0gc2NhbGluZykgCiAgICB9CiAgICAgIAogICAgIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlcwogICAgZ2VuZXMgPC0gYyhnZW5lQSwgZ2VuZUIpCiAgICAgIAogICAgIyMjIyMgWi1zY29yZXMKICAgIG91dHB1dF90YWJsZV9aW1tpXV0gPC0gZXhwclRhYmxlKCBnZW5lcyA9IHVuaXF1ZShnZW5lcyksIGRhdGEgPSBkYXRhLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXVssIGMoIk9uY29nZW5lIiwgIlRTRyIsICJHZXJtbGluZSIpIF0sIGZ1c2lvbl9nZW5lcyA9IHVuaXF1ZShrbm93bl90cmFuc2xvY2F0aW9ucyRnZW5lQSwga25vd25fdHJhbnNsb2NhdGlvbnMkZ2VuZUIgKSwgZXh0X2xpbmtzID0gVFJVRSwgdHlwZSA9ICJ6Iiwgc2NhbGluZyA9IHNjYWxpbmcpW1sxXV0KICAgICAgCiAgICAjIyMjIyBQZXJjZW50aWxlcwogICAgb3V0cHV0X3RhYmxlX3BlcmNbW2ldXSA8LSBleHByVGFibGUoIGdlbmVzID0gdW5pcXVlKGdlbmVzKSwgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGNhbmNlcl9nZW5lcyA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dWywgYygiT25jb2dlbmUiLCAiVFNHIiwgIkdlcm1saW5lIikgXSwgZnVzaW9uX2dlbmVzID0gdW5pcXVlKGtub3duX3RyYW5zbG9jYXRpb25zJGdlbmVBLCBrbm93bl90cmFuc2xvY2F0aW9ucyRnZW5lQiApLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInBlcmMiLCBzY2FsaW5nID0gc2NhbGluZylbWzFdXQogIH0KfQoKIyMjIyMgRGV0YWNoIHBsb3RseSBwYWNrYWdlLiBPdGhlcndpc2UgaXQgY2xhc2hlcyB3aXRoIG90aGVyIGdyYXBoaWNzIGRldmljZXMKZGV0YWNoKCJwYWNrYWdlOnBsb3RseSIsIHVubG9hZD1GQUxTRSkKCiMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQppZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKYGBgCgpgYGB7ciB0b3BfaGl0c19mdXNpb25zX2Rpc3BsYXksIGVjaG89RkFMU0UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDIuNSwgZXZhbCA9IHJ1bkZ1c2lvbkNodW5rLCByZXN1bHRzPSJhc2lzIn0KIyMjIyMgTm93IG9uY2UgdGhlIHBsb3RzIGFyZSByZWFkeSBzaG93IHRoZW0gaW4gc2VwYXJhdGUgdGFicwppZiAoIGdlbmVzX25vID4gMCApIHsKICBmb3IoIGkgaW4gMTpnZW5lc19ubyApIHsKICAgIGdlbmVBIDwtIGFzLnZlY3RvcihmdXNpb25zJGdlbmVBW2ldKQogICAgZ2VuZUIgPC0gYXMudmVjdG9yKGZ1c2lvbnMkZ2VuZUJbaV0pCiAgICAKICAgIGJyZWFrcG9pbnRBIDwtIGFzLnZlY3RvcihmdXNpb25zJGJyZWFrcG9pbnRBW2ldKQogICAgYnJlYWtwb2ludEIgPC0gYXMudmVjdG9yKGZ1c2lvbnMkYnJlYWtwb2ludEJbaV0pCiAgICAKICAgIGNhdCgiXG4jIyMjICIsIHBhc3RlKGZ1c2lvbnMkZ2VuZUFbaV0sIGZ1c2lvbnMkZ2VuZUJbaV0sIHNlcD0iLSIpLCAiXG4iKQogICAgCiAgICAjIyMjIyBDaGVjayBpZiBBcnJpYmEgZnVzaW9uIHBsb3QgZXhpc3RzLiBTa2lwIHRoaXMgc2VjdGlvbiBpZiBpdCBkb2Vzbid0CiAgICBpZiAoIGZpbGUuZXhpc3RzKGdzdWIoIjoiLCAiLiIsIHBhc3RlMChyZXN1bHRzX2RpciwgIi9hcnJpYmEvIiwgbWFrZS5uYW1lcyhwYXN0ZShnZW5lQSwgZ2VuZUIsIHNlcCA9ICJfXyIpKSwgIl8iLCBicmVha3BvaW50QSwgIi0iLCBicmVha3BvaW50QiwgIi5wbmciKSkpICkgewogICAgICBjYXQoIlxuIyMjIyMgRnVzaW9uIGdlbmVzIHZpc3VhbGlzYXRpb25cbiIpCiAgCiAgICAgICMjIyMjIFByZXNlbnQgQXJyaWJhIHBsb3RzIGl0IGluIHRoZSByZXBvcnQKICAgICAgY2F0KHBhc3RlMCgiIVtdKCIsIGdzdWIoIjoiLCAiLiIsIHBhc3RlMChyZXN1bHRzX2RpciwgIi9hcnJpYmEvIiwgbWFrZS5uYW1lcyhwYXN0ZShnZW5lQSwgZ2VuZUIsIHNlcCA9ICJfXyIpKSwgIl8iLCBicmVha3BvaW50QSwgIi0iLCBicmVha3BvaW50QiwgIi5wbmcpIikpKSwgIlxuIikKICAgICAgY2F0KCJcbioqKlxuIikKICAgIH0KICAgIAogICAgY2F0KCJcbiMjIyMjIEZ1c2lvbiBnZW5lcyBleHByZXNzaW9uXG4iKQogICAgY2F0KCJcbm1STkEgZXhwcmVzc2lvbiBsZXZlbHMgb2YgZnVzaW9uIGdlbmVzIGRldGVjdGVkIGluIHBhdGllbnQncyBzYW1wbGUgYW5kIHRoZWlyIGF2ZXJhZ2UgbVJOQSBleHByZXNzaW9uIChaLXNjb3JlKSBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIGNvaG9ydHMuXG4iKQogICAgICAKICAgICMjIyMjIERpc3BsYXkgQ0RGIHBsb3RzIGZvciBlYWNoIGZ1c2lvbiBnZW5lIHBhaXIKICAgIHN1cHByZXNzTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQogICAgICAKICAgIGlmICggZ2VuZUEgJWluJSByb3duYW1lcyhkYXRhKSApIHsKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NkZl9BW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5QbG90IGxlZ2VuZDwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIkRpc3RyaWJ1dGlvbiBvZiBwZXJjZW50aWxlIHZhbHVlcyAoKnktYXhpcyopIGFzIGEgZnVuY3Rpb24gb2YgZXhwcmVzc2lvbiBsZXZlbHMgKFotc2NvcmVzLCAqeC1heGlzKikgb2YgKiIsIGdlbmVBLCAiKiBpbiBwYXRpZW50J3Mgc2FtcGxlICgqYmxhY2sgZG90KikgYW5kIG90aGVyIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0KHMpIChtZWRpYW4gdmFsdWUocykpLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxkZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxzdW1tYXJ5PlJlYWQgY291bnRzPC9zdW1tYXJ5PlxuIikKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NvdW50c19BW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIkJhci1wbG90IGlsbHVzdHJhdGluZyByZWFkIGNvdW50cyBmb3IgKiIsIGdlbmVBLCAiKiBhY3Jvc3MgYWxsIHNhbXBsZXMuIFRoZSAqIiwgZ2VuZUEsICIqIHJlYWQgY291bnQgaW4gcGF0aWVudCdzIHNhbXBsZSBpcyBpbmRpY2F0ZWQgYnkgKmJsYWNrIGJhciouXG4iKSkKICAgICAgY2F0KCJcbjwvZm9udD5cbiIpCiAgICAgIGNhdCgiXG48L2RldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPGRldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPHN1bW1hcnk+RXhwcmVzc2lvbiBkaXN0cmlidXRpb24gcGF0dGVybnM8L3N1bW1hcnk+XG4iKQogICAgICBjYXQocmVuZGVyVGFncyhvdXRwdXRfZGVuc2l0eV9BW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIlBsb3QgaWxsdXN0cmF0aW5nIGRpc3RyaWJ1dGlvbiBvZiBleHByZXNzaW9uIGxldmVscyAoWi1zY29yZXMpIG9mICoiLCBnZW5lQSwgIiogKm9ic2VydmVkKiBhY3Jvc3MgYWxsIHNhbXBsZXMgYWxvbmcgd2l0aCBzaW11bGF0ZWQgKm5vcm1hbCogYW5kICpiaW1vZGFsKiBkaXN0cmlidXRpb25zLiBUaGUgKiIsIGdlbmVBLCAiKiBleHByZXNzaW9uIGxldmVsIG9ic2VydmVkIGluIHBhdGllbnQncyBzYW1wbGUgaXMgaW5kaWNhdGVkIGJ5ICpibGFjayBkb3QqIGluIGVhY2ggZGlzdHJpYnV0aW9uLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCI8YnIgLz4iKQogICAgfSBlbHNlIHsKICAgICAgY2F0KHBhc3RlMCgiXG48c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj5OT1RFPC9zcGFuPiwgZXhwcmVzc2lvbiBkYXRhIGlzIG5vdCBhdmFpbGFibGUgZm9yICIsIGdlbmVBLCAiLlxuIikpCiAgICB9CiAgICAKICAgIGlmICggZ2VuZUIgPT0gZ2VuZUEgKSB7CiAgICAgIGNhdChwYXN0ZTAoIlxuIikpCiAgICB9IGVsc2UgaWYgKCBnZW5lQiAlaW4lIHJvd25hbWVzKGRhdGEpICApIHsKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NkZl9CW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5QbG90IGxlZ2VuZDwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIkRpc3RyaWJ1dGlvbiBvZiBwZXJjZW50aWxlIHZhbHVlcyAoKnktYXhpcyopIGFzIGEgZnVuY3Rpb24gb2YgZXhwcmVzc2lvbiBsZXZlbHMgKFotc2NvcmVzLCAqeC1heGlzKikgb2YgKiIsIGdlbmVCLCAiKiBpbiBwYXRpZW50J3Mgc2FtcGxlICgqYmxhY2sgZG90KikgYW5kIG90aGVyIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0KHMpIChtZWRpYW4gdmFsdWUocykpLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxkZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxzdW1tYXJ5PlJlYWQgY291bnRzPC9zdW1tYXJ5PlxuIikKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NvdW50c19CW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIkJhci1wbG90IGlsbHVzdHJhdGluZyByZWFkIGNvdW50cyBmb3IgKiIsIGdlbmVCLCAiKiBhY3Jvc3MgYWxsIHNhbXBsZXMuIFRoZSAqIiwgZ2VuZUIsICIqIHJlYWQgY291bnQgaW4gcGF0aWVudCdzIHNhbXBsZSBpcyBpbmRpY2F0ZWQgYnkgKmJsYWNrIGJhciouXG4iKSkKICAgICAgY2F0KCJcbjwvZm9udD5cbiIpCiAgICAgIGNhdCgiXG48L2RldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPGRldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPHN1bW1hcnk+RXhwcmVzc2lvbiBkaXN0cmlidXRpb24gcGF0dGVybnM8L3N1bW1hcnk+XG4iKQogICAgICBjYXQocmVuZGVyVGFncyhvdXRwdXRfZGVuc2l0eV9CW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIlBsb3QgaWxsdXN0cmF0aW5nIGRpc3RyaWJ1dGlvbiBvZiBleHByZXNzaW9uIGxldmVscyAoWi1zY29yZXMpIG9mICoiLCBnZW5lQiwgIiogKm9ic2VydmVkKiBhY3Jvc3MgYWxsIHNhbXBsZXMgYWxvbmcgd2l0aCBzaW11bGF0ZWQgKm5vcm1hbCogYW5kICpiaW1vZGFsKiBkaXN0cmlidXRpb25zLiBUaGUgKiIsIGdlbmVCLCAiKiBleHByZXNzaW9uIGxldmVsIG9ic2VydmVkIGluIHBhdGllbnQncyBzYW1wbGUgaXMgaW5kaWNhdGVkIGJ5ICpibGFjayBkb3QqIGluIGVhY2ggZGlzdHJpYnV0aW9uLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbioqKlxuIikKICAgIH0gZWxzZSB7CiAgICAgIGNhdChwYXN0ZTAoIlxuPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+Tk9URTwvc3Bhbj4sIGV4cHJlc3Npb24gZGF0YSBpcyBub3QgYXZhaWxhYmxlIGZvciAiLCBnZW5lQiwgIi5cbiIpKQogICAgfQogICAgICAKICAgIGNhdCgiXG4jIyMjIyBTdW1tYXJ5IHRhYmxlIHsudGFic2V0fVxuIikKICAgIGNhdCgiXG4jIyMjIyMgUGVyY2VudGlsZXNcbiIpCiAgICBjYXQocmVuZGVyVGFncyhvdXRwdXRfdGFibGVfcGVyY1tbaV1dKSRodG1sKQogICAgY2F0KCI8YnIgLz4iKQogICAgY2F0KCJcbjxkZXRhaWxzPlxuIikKICAgIGNhdCgiXG48c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+XG4iKQogICAgY2F0KCJcbjxmb250IHNpemU9XCIyXCI+XG4iKQogICAgY2F0KCJcblRoZSA8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj5SRUQ8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqaGlnaCBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBhbmQgPHNwYW4gc3R5bGU9XCJjb2xvcjojMDAwMGZmXCI+QkxVRTwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipsb3cgZXhwcmVzc2lvbioqIChwZXJjZW50aWxlKSB2YWx1ZXMgaW4gaW5kaXZpZHVhbCBzYW1wbGUgZ3JvdXAuIFRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzICIsIGNvbXBfY2FuY2VyX2dyb3VwLCAiICoqKSBjb2x1bW4gaWxsdXN0cmF0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwZXJjZW50aWxlcyBpbiBwYXRpZW50IHNhbXBsZSBhbmQgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQgZm9yIGVhY2ggZnVzaW9uIGdlbmUuIEdlbmVzIGNvbnNpZGVyZWQgdG8gYmUgb25jb2dlbmVzIG9yIHR1bW91ciBzdXBwcmVzc29yIGdlbmVzLCBhY2NvcmRpbmcgdG8gW09uY29LQl0oaHR0cDovL29uY29rYi5vcmcvIy9jYW5jZXJHZW5lcyl7dGFyZ2V0PVwiX2JsYW5rXCJ9IGRhdGFiYXNlLCBhcmUgYWxzbyBpbmRpY2F0ZWQuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyAiLCBjb21wX2NhbmNlcl9ncm91cCwgIioqKSBjb2x1bW4uICpUU0cqIC0gdHVtb3VyIHN1cHByZXNzb3IgZ2VuZVxuIikKICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAKICAgIGlmICggbGVuZ3RoKGdlbmVzKSA+IDIwMDAgKSB7IAogICAgICAgIGNhdChwYXN0ZTAoIjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCB0aGUgdGFibGUgd2FzIHRydW5jYXRlZCB0byAyMDAwIGVudHJpZXMuIikpIAogICAgfQogICAgCiAgICBjYXQoIlxuKioqXG4iKQogICAgCiAgICBjYXQoIlxuIyMjIyMjIFotc2NvcmVzXG4iKQogICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X3RhYmxlX1pbW2ldXSkkaHRtbCkKICAgIGNhdCgiPGJyIC8+IikKICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICBjYXQoIlxuPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5PlxuIikKICAgIGNhdCgiXG48Zm9udCBzaXplPVwiMlwiPlxuIikKICAgIGNhdChwYXN0ZTAoIlxuVGhlIDxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPlJFRDwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipoaWdoIGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGFuZCA8c3BhbiBzdHlsZT1cImNvbG9yOiMwMDAwZmZcIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKFotc2NvcmUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgIiwgIGNvbXBfY2FuY2VyX2dyb3VwLCAiKiopIGNvbHVtbiBpbGx1c3RyYXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFotc2NvcmVzIGluIHBhdGllbnQgc2FtcGxlIGFuZCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydCBmb3IgZWFjaCBmdXNpb24gZ2VuZS4gR2VuZXMgY29uc2lkZXJlZCB0byBiZSBvbmNvZ2VuZXMgb3IgdHVtb3VyIHN1cHByZXNzb3IgZ2VuZXMsIGFjY29yZGluZyB0byBbT25jb0tCXShodHRwOi8vb25jb2tiLm9yZy8jL2NhbmNlckdlbmVzKXt0YXJnZXQ9XCJfYmxhbmtcIn0gZGF0YWJhc2UsIGFyZSBhbHNvIGluZGljYXRlZC4gR2VuZXMgYXJlIG9yZGVyZWQgYnkgKipkZWNyZWFzaW5nKiogYWJzb2x1dGUgdmFsdWVzIGluIHRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzICIsIGNvbXBfY2FuY2VyX2dyb3VwLCAiKiopIGNvbHVtbi4gKlRTRyogLSB0dW1vdXIgc3VwcHJlc3NvciBnZW5lXG4iKSkKICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAKICAgIGlmICggbGVuZ3RoKGdlbmVzKSA+IDIwMDAgKSB7IAogICAgICAgIGNhdChwYXN0ZTAoIjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCB0aGUgdGFibGUgd2FzIHRydW5jYXRlZCB0byAyMDAwIGVudHJpZXMuIikpIAogICAgfQogICAgCiAgICBjYXQoIlxuKioqXG4iKQogIH0KICAjIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKICAKfSBlbHNlIHsKICBjYXQoIlxuTm8gYWx0ZXJhdGlvbnMgd2VyZSByZXBvcnRlZC5cbiIpCiAgIGNhdCgiXG4qKipcbiIpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0obGlzdCA9IGxzKHBhdHRlcm49J15vdXRwdXQqJykpCmBgYAoKKioqCgojIyBTdHJ1Y3R1cmFsIHZhcmlhbnRzCgptUk5BIGV4cHJlc3Npb24gbGV2ZWxzIG9mIGdlbmVzIGxvY2F0ZWQgd2l0aGluIGRldGVjdGVkIHN0cnVjdHVyYWwgdmFyaWFudHMgKFNWcyksIG9idGFpbmVkIGZyb20gW01hbnRhXShodHRwczovL2dpdGh1Yi5jb20vSWxsdW1pbmEvbWFudGEpe3RhcmdldD0iX2JsYW5rIn0gU1YgY2FsbGVyLCBpbiBwYXRpZW50J3Mgc2FtcGxlIGFuZCB0aGVpciBhdmVyYWdlIG1STkEgZXhwcmVzc2lvbiBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIGNvaG9ydHMuCgpgciBpZiAoICFydW5TVnNDaHVuayApIHsgYygiU1ZzIGluZm9ybWF0aW9uIGZvciB0aGlzIHNhbXBsZSBpcyAqKk5PVCBBVkFJTEFCTEUqKiIpIH1gIAoKIyMjIC0gU3VtbWFyeSB0YWJsZSB7LnRhYnNldH0KCk91dCBvZiB0aGUgYHIgaWYgKCBydW5TVnNDaHVuayApIHsgbGVuZ3RoKHVuaXF1ZShtYW50YV9zdiRHZW5lKSkgfSBgIGdlbmVzIGFmZmVjdGVkIGJ5IGByIGlmICggcnVuU1ZzQ2h1bmsgKSB7IG5yb3cobWFudGFfc3YpIH1gIFNWcywgdGhlIGV4cHJlc3Npb24gb2YgKipgciBpZiAoIHJ1blNWc0NodW5rICkgeyBsZW5ndGgod2hpY2godW5pcXVlKG1hbnRhX3N2JEdlbmUpICVpbiUgcm93bmFtZXMocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pKSkgfSBlbHNlIHsgbGVuZ3RoKE5VTEwpIH1gKiogd2FzIHJlbGlhYmx5IG1lYXN1cmVkIGluIHBhdGllbnQncyBzYW1wbGUuIFRoZSByZW1haW5pbmcgYHIgaWYgKCBydW5TVnNDaHVuayApIHsgbGVuZ3RoKHdoaWNoKHVuaXF1ZShtYW50YV9zdiRHZW5lKSAlIWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKSB9YCBnZW5lcyBhcmUgZWl0aGVyIG5vdCBleHByZXNzZWQgb3IgdGhlaXIgZXhwcmVzc2lvbiBsZXZlbCBpcyB0b28gbG93IHRvIGJlIGRldGVjdGVkIChpbmRpY2F0ZWQgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzKS4KCgojIyMjIFBlcmNlbnRpbGVzCgpgYGB7ciBzdl9nZW5lc190YWJsZV9wZXJjLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBydW5TVnNDaHVua30KIyMjIyMgVXBkYXRlIE15U1FMIGNvbW1lbmQgdG8gcG9wdWxhdGUgUk5BLXNlcSBkYXRhIHBvcnRhbApteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGUsICIsU3RydWN0dXJhbCB2YXJpYW50cyIpCm15c3FsX3BvcHVsYXRlX3VwZGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGVfdXBkYXRlLCAiLFN0cnVjdHVyYWwgdmFyaWFudHMiKQoKIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBjYW5jZXIgZ2VuZXMgZnJvbSBPbmNvS0IgYW5kIFVNQ0NSIChodHRwczovL2dpdGh1Yi5jb20vdmxhZHNhdmVsaWV2L05HU19VdGlscy9ibG9iL21hc3Rlci9uZ3NfdXRpbHMvcmVmZXJlbmNlX2RhdGEva2V5X2dlbmVzL3VtY2NyX2NhbmNlcl9nZW5lcy4yMDE5LTAzLTIwLnRzdikKdGFyZ2V0cyA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJzYW1wbGVfYW5ub3QiXV0KZGF0YSA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXQoKIyMjIyMgQ29uc2lkZXIgb25seSBTVnMgd2l0aCBrbm93biBnZW5lcyBhbmQgdGhvc2UgaW4gTUFOVEEgb3V0cHV0IGZvciB3aGljaCB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgd2VyZSBtZWFzdXJlZApnZW5lcyA8LSB1bmlxdWUobWFudGFfc3YkR2VuZSkKZ2VuZXMgPC0gZ2VuZXNbIGdlbmVzICVpbiUgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV0kU1lNQk9MIF0KCiMjIyMjIERlYWwgd2l0aCBubyBnZW5lcyBvciB3aGVuIG1vcmUgdGhhbiAxMCBnZW5lcyBhcmUgb2YgaW50ZXJlc3QKaWYgKCBsZW5ndGgoZ2VuZXMpID09IDAgKSB7CiAgZ2VuZXMgPC0gTlVMTAogIGxpbWl0X2dlbmVzIDwtIEZBTFNFCiAgZ2VuZXNfbm8gPC0gMAp9IGVsc2UgaWYgKCBsZW5ndGgoZ2VuZXMpID4gcGFyYW1zJHRvcF9nZW5lcyApIHsKICBsaW1pdF9nZW5lcyA8LSBUUlVFCiAgZ2VuZXNfbm8gPC0gcGFyYW1zJHRvcF9nZW5lcwp9IGVsc2UgewogIGxpbWl0X2dlbmVzIDwtIEZBTFNFCiAgZ2VuZXNfbm8gPC0gbGVuZ3RoKGdlbmVzKQp9Cgpzdl9nZW5lcy5leHByLnBlcmMgPC0gZXhwclRhYmxlKCBnZW5lcyA9IGdlbmVzLCBkYXRhID0gZGF0YSwgc3ZfZGF0YSA9IG1hbnRhX3N2LCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXVssIGMoIk9uY29nZW5lIiwgIlRTRyIsICJGdXNpb24iLCAiR2VybWxpbmUiKSBdLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInBlcmMiLCBzY2FsaW5nID0gc2NhbGluZykKCiMjIyMjIFByZXNlbnQgdGhlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZQpzdl9nZW5lcy5leHByLnBlcmNbWzFdXQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9c3ZfZ2VuZXMuZXhwci5wZXJjW1sxXV0sIGZpbGU9cGFzdGUoZXhwclRhYmxlRGlyLCAic3ZfZ2VuZXMuZXhwci5wZXJjLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmhpZ2ggZXhwcmVzc2lvbioqIChwZXJjZW50aWxlKSB2YWx1ZXMgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojMDAwMGZmIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbiBpbGx1c3RyYXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHBlcmNlbnRpbGVzIGluIHBhdGllbnQgc2FtcGxlIGFuZCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydCBmb3IgZWFjaCBnZW5lLiBHZW5lcyBjb25zaWRlcmVkIHRvIGJlIG9uY29nZW5lcyBvciB0dW1vdXIgc3VwcHJlc3NvciBnZW5lcywgYWNjb3JkaW5nIHRvIFtPbmNvS0JdKGh0dHA6Ly9vbmNva2Iub3JnLyMvY2FuY2VyR2VuZXMpe3RhcmdldD0iX2JsYW5rIn0gZGF0YWJhc2UsIGFyZSBhbHNvIGluZGljYXRlZC4gR2VuZXMgYXJlIG9yZGVyZWQgYnkgKippbmNyZWFzaW5nIFNWIHNjb3JlKiogYW5kIHRoZW4gYnkgKipkZWNyZWFzaW5nKiogYWJzb2x1dGUgdmFsdWVzIGluIHRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW5zLiAqVFNHKiAtIHR1bW91ciBzdXBwcmVzc29yIGdlbmUKCioqVGllcioqOiBTViBwcmlvcml0eSBzY29yZSBiYXNlZCBvbiBBc3RyYVplbmVjYSBbc2ltcGxlX3N2X2Fubm90YXRpb24ucHldKGh0dHBzOi8vZ2l0aHViLmNvbS9Bc3RyYVplbmVjYS1OR1Mvc2ltcGxlX3N2X2Fubm90YXRpb24vYmxvYi9tYXN0ZXIvc2ltcGxlX3N2X2Fubm90YXRpb24ucHkjTDIxLUwzNikgc2NyaXB0OyAqMSA9IGhpZ2gqIGFuZCAqNCA9IGxvdyBwcmlvcml0eSoKCjwvZm9udD4KPC9kZXRhaWxzPgoKYHIgaWYgKCBydW5TVnNDaHVuayAmJiBsZW5ndGgoZ2VuZXMpID4gMjAwMCApIHsgYyhwYXN0ZTAoIjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCB0aGUgdGFibGUgd2FzIHRydW5jYXRlZCB0byAyMDAwIGVudHJpZXMuIikpIH0gZWxzZSB7IGNhdCgiIikgfWAKCioqKgoKIyMjIyBaLXNjb3JlcwoKYGBge3Igc3ZfZ2VuZXNfdGFibGUsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blNWc0NodW5rfQojIyMjIyBHZW5lcmF0ZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUgZm9yIGNhbmNlciBnZW5lcyBmcm9tIE9uY29LQiBhbmQgVU1DQ1IgKGh0dHBzOi8vZ2l0aHViLmNvbS92bGFkc2F2ZWxpZXYvTkdTX1V0aWxzL2Jsb2IvbWFzdGVyL25nc191dGlscy9yZWZlcmVuY2VfZGF0YS9rZXlfZ2VuZXMvdW1jY3JfY2FuY2VyX2dlbmVzLjIwMTktMDMtMjAudHN2KQpzdl9nZW5lcy5leHByLnogPC0gZXhwclRhYmxlKCBnZW5lcyA9IGdlbmVzLCBkYXRhID0gZGF0YSwgc3ZfZGF0YSA9IG1hbnRhX3N2LCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIGNhbmNlcl9nZW5lcyA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dWywgYygiT25jb2dlbmUiLCAiVFNHIiwgIkZ1c2lvbiIsICJHZXJtbGluZSIpIF0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInoiLCBzY2FsaW5nID0gc2NhbGluZylbWzFdXQoKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlCnN2X2dlbmVzLmV4cHIuegoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9c3ZfZ2VuZXMuZXhwci56LCBmaWxlPXBhc3RlKGV4cHJUYWJsZURpciwgInN2X2dlbmVzLmV4cHIuei5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0oc3ZfZ2VuZXMuZXhwci56KQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojZmYwMDAwIj5SRUQ8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqaGlnaCBleHByZXNzaW9uKiogKFotc2NvcmUpIHZhbHVlcyBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiMwMDAwZmYiPkJMVUU8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqbG93IGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGluIGluZGl2aWR1YWwgc2FtcGxlIGdyb3VwLiBUaGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uIGlsbHVzdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gWi1zY29yZXMgaW4gcGF0aWVudCBzYW1wbGUgYW5kIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0IGZvciBlYWNoIGdlbmUuIEdlbmVzIGNvbnNpZGVyZWQgdG8gYmUgb25jb2dlbmVzIG9yIHR1bW91ciBzdXBwcmVzc29yIGdlbmVzLCBhY2NvcmRpbmcgdG8gW09uY29LQl0oaHR0cDovL29uY29rYi5vcmcvIy9jYW5jZXJHZW5lcyl7dGFyZ2V0PSJfYmxhbmsifSBkYXRhYmFzZSwgYXJlIGFsc28gaW5kaWNhdGVkLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKmluY3JlYXNpbmcgU1Ygc2NvcmUqKiBhbmQgdGhlbiBieSAqKmRlY3JlYXNpbmcqKiBhYnNvbHV0ZSB2YWx1ZXMgaW4gdGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbi4gKlRTRyogLSB0dW1vdXIgc3VwcHJlc3NvciBnZW5lCgoqKlRpZXIqKjogU1YgcHJpb3JpdHkgc2NvcmUgYmFzZWQgb24gQXN0cmFaZW5lY2EgW3NpbXBsZV9zdl9hbm5vdGF0aW9uLnB5XShodHRwczovL2dpdGh1Yi5jb20vQXN0cmFaZW5lY2EtTkdTL3NpbXBsZV9zdl9hbm5vdGF0aW9uL2Jsb2IvbWFzdGVyL3NpbXBsZV9zdl9hbm5vdGF0aW9uLnB5I0wyMS1MMzYpe3RhcmdldD0iX2JsYW5rIn0gc2NyaXB0OyAqKjEgPSBoaWdoKiogYW5kICoqNCA9IGxvdyBwcmlvcml0eSoqCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCmByIGlmICggcnVuU1ZzQ2h1bmsgJiYgbGVuZ3RoKGdlbmVzKSA+IDIwMDAgKSB7IGMocGFzdGUwKCI8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj5OT1RFPC9zcGFuPiwgdGhlIHRhYmxlIHdhcyB0cnVuY2F0ZWQgdG8gMjAwMCBlbnRyaWVzLiIpKSB9IGVsc2UgeyBjYXQoIiIpIH1gCgoqKioKCiMjIyAtIEV4cHJlc3Npb24gcHJvZmlsZXMgey50YWJzZXR9CgpgciBpZiAoIGV4aXN0cygibGltaXRfZ2VuZXMiKSApIHsgaWYgKCBsaW1pdF9nZW5lcyApIHsgYyhwYXN0ZTAoIkV4cHJlc3Npb24gcHJvZmlsZXMgZm9yICIsIGdlbmVzX25vLCAiIFNWcy1hZmZlY3RlZCBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0IHByaW9yaXR5IChsb3cgW3RpZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9Bc3RyYVplbmVjYS1OR1Mvc2ltcGxlX3N2X2Fubm90YXRpb24vYmxvYi9tYXN0ZXIvc2ltcGxlX3N2X2Fubm90YXRpb24ucHkjTDIxLUwzNil7dGFyZ2V0PVwiX2JsYW5rXCJ9KSBhbmQgZGVtb25zdHJhdGluZyB0aGUgZ3JlYXRlc3QgZGlmZmVyZW5jZSBpbiBtUk5BIGV4cHJlc3Npb24gKHBlcmNlbnRpbGUpIHZhbHVlcyBiZXR3ZWVuIHBhdGllbnQncyBzYW1wbGUgYW5kIHRoZSBhdmVyYWdlIG1STkEgZXhwcmVzc2lvbiBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIHBhdGllbnRzLiIpKSB9IGVsc2UgeyBjYXQoIiAiKSB9fWAKCmBgYHtyIGNkZl9wbG90c19zdiwgZWNobz1GQUxTRSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMywgZXZhbCA9IHJ1blNWc0NodW5rLCByZXN1bHRzPSJhc2lzIn0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCiMjIyMjIEdlbmVyYXRlIGVtcGlyaWNhbCBjdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiAoRUNERikgcGxvdCBpbGx1c3RyYXRpbmcgbVJOQSBleHByZXNzaW9uIGxldmVsIGZvciB0aGUgZ2VuZXMgb2YgaW50ZXJlc3QgaW4gdGhlIGNvbnRleHQgb2YgdGhlIG92ZXJhbGwgbVJOQSBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbgpvdXRwdXRfY2RmIDwtIGxpc3QoKQpvdXRwdXRfY291bnRzIDwtIGxpc3QoKQpvdXRwdXRfZGVuc2l0eSA8LSBsaXN0KCkKZ2VuZXMgPC0gdW5pcXVlKHN2X2dlbmVzLmV4cHIucGVyY1tbMl1dJFNZTUJPTCkKCiMjIyMjIEZvciBlYWNoIGdlbmUgZ2VuZXJhdGUgKDEpIENERiBwbG90IGFuZCBhZGQgYm94cGxvdCBiZWxvdyB0byBzaG93IHRoZSBkYXRhIHZhcmlhbmNlIGZvciBzZWxlY3RlZCBnZW5lIGluIGluZGl2aWR1YWwgZ3JvdXBzLCAoMikgYmFyLXBsb3Qgb2YgcmVhZCBjb3VudCBkYXRhIGFjcm9zcyBhbGwgc2FtcGxlcyBhbmQgKDMpIGRlbnNpdHkgcGxvdCB0byBkZW1vbnN0cmF0ZSBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbiBpbiBpbnZlc3RpZ2F0ZWQgc2FtcGxlIApmb3IoIGkgaW4gMTpnZW5lc19ubyApIHsKICBpZiAoIGdlbmVzX25vID4gMCAmJiBnZW5lc1tpXSAlaW4lIHJvd25hbWVzKGRhdGEpICkgewogICAgCiAgICAjIyMjIyBDREYgcGxvdAogICAgb3V0cHV0X2NkZltbaV1dIDwtIGNkZlBsb3QoZ2VuZSA9IGdlbmVzW2ldLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgYWRkQm94UGxvdCA9IFRSVUUsIHNjYWxpbmcgPSBzY2FsaW5nLCByZXBvcnRfZGlyID0gcmVzdWx0c19kaXIpCiAgICAKICAgICMjIyMjIEJhci1wbG90IG9mIHJlYWQgY291bnRzCiAgICAjIyMjIyBGaXJzdCBtYXAgdGhlIGdlbmUgc3ltYm9sIHRvIEVuc21lYmwgSUQgKHVzZWQgaW4gdGhlIGNvdW50cyBkYXRhKQogICAgZ2VuZXMuRU5TRU1CTCA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRFTlNFTUJMWyByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRTWU1CT0wgPT0gIGdlbmVzW2ldIF0KICAgIAogICAgb3V0cHV0X2NvdW50c1tbaV1dIDwtIGJhclBsb3QoZ2VuZSA9IGdlbmVzLkVOU0VNQkwsIGRhdGEgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dLCB5X3RpdGxlID0gIkNvdW50cyIsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAgKQogICAgCiAgICAjIyMjIyBEZW5zaXR5IHBsb3QgLSBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbgogICAgb3V0cHV0X2RlbnNpdHlbW2ldXSA8LSBkZW5zaXR5UGxvdChnZW5lID0gZ2VuZXNbaV0sIGRhdGEgPSBkYXRhLCBtYWluX3RpdGxlPSAiIiwgeF90aXRsZSA9ICJaLXNjb3JlIiwgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBkaXN0cmlidXRpb25zID0gYygibm9ybWFsIiwgImJpbW9kYWwiKSwgc2NhbGluZyA9IHNjYWxpbmcpIAogIH0KfQoKIyMjIyMgTm93IG9uY2UgdGhlIHBsb3RzIGFyZSByZWFkeSBzaG93IHRoZW0gaW4gc2VwYXJhdGUgdGFicwppZiAoIGdlbmVzX25vICE9IDAgKSB7CiAgZm9yKCBpIGluIDE6Z2VuZXNfbm8gKXsKICAgIGlmICggZ2VuZXNbaV0gJWluJSByb3duYW1lcyhkYXRhKSApIHsKICAgICAgY2F0KCJcbiMjIyMgIiwgZ2VuZXNbaV0sICJcbiIpCiAgICAgIGNhdChyZW5kZXJUYWdzKG91dHB1dF9jZGZbW2ldXSkkaHRtbCkKICAgICAgY2F0KCJcbjxkZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbjxzdW1tYXJ5PlBsb3QgbGVnZW5kPC9zdW1tYXJ5PlxuIikKICAgICAgY2F0KCI8Zm9udCBzaXplPVwiMlwiPlxuIikKICAgICAgY2F0KHBhc3RlMCgiKipUb3AgcGFuZWwqKjogZGlzdHJpYnV0aW9uIG9mIHBlcmNlbnRpbGUgdmFsdWVzICgqeS1heGlzKikgYXMgYSBmdW5jdGlvbiBvZiBleHByZXNzaW9uIGxldmVscyAoWi1zY29yZXMsICp4LWF4aXMqKSBmb3IgKiIsIGdlbmVzW2ldLCAiKiBpbiBwYXRpZW50J3Mgc2FtcGxlICgqYmxhY2sgZG90KikgYW5kIG90aGVyIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0KHMpIChtZWRpYW4gdmFsdWUocykpLlxuXG4iKSkKICAgICAgY2F0KHBhc3RlMCgiKipCb3R0b20gcGFuZWwqKjogYm94LXBsb3QgcHJlc2VudGluZyBleHByZXNzaW9uIGxldmVsIChaLXNjb3JlKSBvZiAqIiwgZ2VuZXNbaV0sICIqIGluIHBhdGllbnQncyBzYW1wbGUgKCpibGFjayBkb3QqKSBhbmQgaXRzIGV4cHJlc3Npb24gbGV2ZWxzIG9ic2VydmVkIGFjcm9zcyBzYW1wbGVzIGZyb20gb3RoZXIgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQocykuXG4iKSkKICAgICAgY2F0KCJcbjwvZm9udD5cbiIpCiAgICAgIGNhdCgiXG48L2RldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPGRldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPHN1bW1hcnk+UmVhZCBjb3VudHM8L3N1bW1hcnk+XG4iKQogICAgICBjYXQocmVuZGVyVGFncyhvdXRwdXRfY291bnRzW1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIkJhci1wbG90IGlsbHVzdHJhdGluZyByZWFkIGNvdW50cyBmb3IgKiIsIGdlbmVzW2ldLCAiKiBhY3Jvc3MgYWxsIHNhbXBsZXMuIFRoZSAqIiwgZ2VuZXNbaV0sICIqIHJlYWQgY291bnQgaW4gcGF0aWVudCdzIHNhbXBsZSBpcyBpbmRpY2F0ZWQgYnkgKmJsYWNrIGJhciouXG4iKSkKICAgICAgY2F0KCJcbjwvZm9udD5cbiIpCiAgICAgIGNhdCgiXG48L2RldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPGRldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPHN1bW1hcnk+RXhwcmVzc2lvbiBkaXN0cmlidXRpb24gcGF0dGVybnM8L3N1bW1hcnk+XG4iKQogICAgICBjYXQocmVuZGVyVGFncyhvdXRwdXRfZGVuc2l0eVtbaV1dKSRodG1sKQogICAgICBjYXQoIjxmb250IHNpemU9XCIyXCI+XG4iKQogICAgICBjYXQocGFzdGUwKCJQbG90IGlsbHVzdHJhdGluZyBkaXN0cmlidXRpb24gb2YgZXhwcmVzc2lvbiBsZXZlbHMgKFotc2NvcmVzKSBvZiAqIiwgZ2VuZXNbaV0sICIqICpvYnNlcnZlZCogYWNyb3NzIGFsbCBzYW1wbGVzIGFsb25nIHdpdGggc2ltdWxhdGVkICpub3JtYWwqIGFuZCAqYmltb2RhbCogZGlzdHJpYnV0aW9ucy4gVGhlICoiLCBnZW5lc1tpXSwgIiogZXhwcmVzc2lvbiBsZXZlbCBvYnNlcnZlZCBpbiBwYXRpZW50J3Mgc2FtcGxlIGlzIGluZGljYXRlZCBieSAqYmxhY2sgZG90KiBpbiBlYWNoIGRpc3RyaWJ1dGlvbi5cbiIpKQogICAgICBjYXQoIlxuPC9mb250PlxuIikKICAgICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG4qKipcbiIpCiAgICB9IGVsc2UgewogICAgICBjYXQoIlxuIyMjIyAiLCBnZW5lc1tpXSwgIlxuIikKICAgICAgY2F0KCJcbjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCBleHByZXNzaW9uIGRhdGEgaXMgbm90IGF2YWlsYWJsZSBmb3IgdGhhdCBnZW5lLlxuIikKICAgICAgY2F0KCJcbioqKlxuIikKICAgIH0KICB9CiAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCiAgCn0gZWxzZSB7CiAgY2F0KCJcbk5vIGFsdGVyYXRpb25zIHdlcmUgcmVwb3J0ZWQuXG4iKQogICBjYXQoIlxuKioqXG4iKQp9CgojIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGxpc3QgPSBscyhwYXR0ZXJuPSdeb3V0cHV0KicpKQpybShsaW1pdF9nZW5lcykKYGBgCgpgciBpZiAoICFydW5TVnNDaHVuayApIHsgYygiKioqIikgfSBlbHNlIHsgYygiICIpIH1gCgojIyBDTiBhbHRlcmVkIGdlbmVzCgpTZWN0aW9uIG92ZXJsYXlpbmcgdGhlIG1STkEgZXhwcmVzc2lvbiBkYXRhIHdpdGggcGVyLWdlbmUgc29tYXRpYyBjb3B5LW51bWJlciAoQ04pIGRhdGEgKGZyb20gW1BVUlBMRV0oaHR0cHM6Ly9naXRodWIuY29tL2hhcnR3aWdtZWRpY2FsL2htZnRvb2xzL3RyZWUvbWFzdGVyL3B1cml0eS1wbG9pZHktZXN0aW1hdG9yKXt0YXJnZXQ9Il9ibGFuayJ9KSwgYXMgd2VsbCBhcyBTTlZzL2luZGVscyBhbmQgU1ZzIGRhdGEsIGlmIGF2YWlsYWJsZS4KCmByIGlmICggIXJ1blB1cnBsZUNodW5rICkgeyBjKCJDTiBpbmZvcm1hdGlvbiBmb3IgdGhpcyBzYW1wbGUgaXMgKipOT1QgQVZBSUxBQkxFKiouIikgfWAKCiMjIyAtIEdlbm9taWMgdmlldwoKYHIgaWYgKHJ1blB1cnBsZUNodW5rKSB7IGxlbmd0aChyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJleHByX211dF9jbl9kYXRhX2FsbCJdXVssMV0pfSBlbHNlIHsgY2F0KCIwIikgfWAgZ2VuZXMgd2l0aCBhdmFpbGFibGUgQ04gZGF0YSAoKnktYXhpcyopIGFyZSBwcmVzZW50ZWQgaW4gdGhlIGdlbm9taWMgY29udGV4dCAoKngtYXhpcyopLiAqKmByIGlmIChydW5QdXJwbGVDaHVuaykgeyBsZW5ndGgocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YSJdXVssMV0pIH0gZWxzZSB7IGxlbmd0aChOVUxMKSB9YCoqIG9mIHRoZW0gKGluZGljYXRlZCBieSAqdmFyaW91cyBjb2xvdXJzKikgYXJlIFtDYW5jZXIgZ2VuZXNdIGFuZCBhcmUgZ2FpbmVkIGByIGlmICggcnVuUHVycGxlQ2h1bmsgKSB7IHBhc3RlMCgiKENOIHZhbHVlcyA+PSAiLCBjbl90b3AsICIpIikgfWAgb3IgbG9zdCBgciBpZiAoIHJ1blB1cnBsZUNodW5rICkgeyBwYXN0ZTAoIihDTiB2YWx1ZXMgPTwgIiwgY25fYm90dG9tLCAiKSIpIH1gLiBBbGwgb3RoZXIgZ2VuZXMgYXJlIG1hcmtlZCBpbiAqPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPmdyYXk8L3NwYW4+KiBvciAqYmxhY2sqLgoKYGBge3IgY25fZ2Vub21pY192aWV3LCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDguMywgZmlnLmhlaWdodCA9IDQsIGV2YWwgPSBydW5QdXJwbGVDaHVua30KIyMjIyMgVXBkYXRlIE15U1FMIGNvbW1lbmQgdG8gcG9wdWxhdGUgUk5BLXNlcSBkYXRhIHBvcnRhbApteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGUsICIsQ04gYWx0ZXJlZCBnZW5lcyIpCm15c3FsX3BvcHVsYXRlX3VwZGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGVfdXBkYXRlLCAiLENOIGFsdGVyZWQgZ2VuZXMiKQoKIyMjIyMgR2VuZXJhdGUgZ2Vub21pYyB2aWV3IHBsb3Qgd2l0aCBwZXItZ2VuZSBDTiB2YWx1ZXMgKHktYXhpcykgYWxvbmcgY2hyb21vc29tYWwgY29vcmRpbmF0ZXMgKHgtYXhpcykKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KG1hbmhhdHRhbmx5KSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCgpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImV4cHJfbXV0X2NuX2RhdGFfYWxsIl1dCmRhdGEuc3ViIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImV4cHJfbXV0X2NuX2RhdGEiXV0KCiMjIyMjIEFkZCBTTlZzCmlmICggcnVuUGNnckNodW5rICkgewogIGRhdGEkQWx0ZXJhdGlvbnMgPC0gYXMuY2hhcmFjdGVyKGRhdGEkQWx0ZXJhdGlvbnMpCiAgZGF0YS5zdWIkQWx0ZXJhdGlvbnMgPC0gYXMuY2hhcmFjdGVyKGRhdGEuc3ViJEFsdGVyYXRpb25zKQp9CgojIyMjIyBBZGQgZnVzaW9uIGdlbmVzCmlmICggcnVuRnVzaW9uQ2h1bmsgKSB7CiAgCiAgIyMjIyMgQ2hhbmdlIHRoZSBhbHRlcmF0aW9uIHR5cGUgdG8gImZ1c2lvbiIgZm9yIGZ1c2lvbiBnZW5lcwogIGRhdGEkQWx0ZXJhdGlvbnNbIGRhdGEkR2VuZSAlaW4lIGZ1c2lvbnMkZ2VuZUEgIF0gPC0gcGFzdGUwKCBkYXRhJEFsdGVyYXRpb25zWyBkYXRhJEdlbmUgJWluJSBmdXNpb25zJGdlbmVBICBdLCAiOyBGdXNpb24iKQogIGRhdGEuc3ViJEFsdGVyYXRpb25zWyBkYXRhLnN1YiRHZW5lICVpbiUgZnVzaW9ucyRnZW5lQSAgXSA8LSBwYXN0ZTAoIGRhdGEuc3ViJEFsdGVyYXRpb25zWyBkYXRhLnN1YiRHZW5lICVpbiUgZnVzaW9ucyRnZW5lQSAgXSwgIjsgRnVzaW9uIikKICBkYXRhJEFsdGVyYXRpb25zWyBkYXRhJEdlbmUgJWluJSBmdXNpb25zJGdlbmVCICBdIDwtIHBhc3RlMCggZGF0YSRBbHRlcmF0aW9uc1sgZGF0YSRHZW5lICVpbiUgZnVzaW9ucyRnZW5lQiAgXSwgIjsgRnVzaW9uIikKICBkYXRhLnN1YiRBbHRlcmF0aW9uc1sgZGF0YS5zdWIkR2VuZSAlaW4lIGZ1c2lvbnMkZ2VuZUIgIF0gPC0gcGFzdGUwKCBkYXRhLnN1YiRBbHRlcmF0aW9uc1sgZGF0YS5zdWIkR2VuZSAlaW4lIGZ1c2lvbnMkZ2VuZUIgIF0sICI7IEZ1c2lvbiIpCn0KICAgICAgICAgICAgCiMjIyMjIEFkZCBnZW5lcyBpbnZvbHZlZCBpbiBTVnMgKGlmIGRhdGEgYXZhaWxhYmxlKQppZiAoIHJ1blNWc0NodW5rICkgewogIAogICMjIyMjIENoYW5nZSB0aGUgYWx0ZXJhdGlvbiB0eXBlIHRvICJmdXNpb24iIGZvciBmdXNpb24gZ2VuZXMKICBkYXRhJEFsdGVyYXRpb25zWyBkYXRhJEdlbmUgJWluJSB1bmlxdWUobWFudGFfc3YkR2VuZSkgIF0gPC0gcGFzdGUwKCBkYXRhJEFsdGVyYXRpb25zWyBkYXRhJEdlbmUgJWluJSB1bmlxdWUobWFudGFfc3YkR2VuZSkgIF0sICI7IFNWIikKICAjIyMjIyBDaGFuZ2UgdGhlIGFsdGVyYXRpb24gdHlwZSB0byAiZnVzaW9uIiBmb3IgZnVzaW9uIGdlbmVzCiAgZGF0YS5zdWIkQWx0ZXJhdGlvbnNbIGRhdGEuc3ViJEdlbmUgJWluJSB1bmlxdWUobWFudGFfc3YkR2VuZSkgIF0gPC0gcGFzdGUwKCBkYXRhLnN1YiRBbHRlcmF0aW9uc1sgZGF0YS5zdWIkR2VuZSAlaW4lIHVuaXF1ZShtYW50YV9zdiRHZW5lKSAgXSwgIjsgU1YiKQp9CgojIyMjIyBSZW1vdmUgYWx0YXJhdGlvbiBzdGF0dXMgIk5vbmUiIGZvciBnZW5lIHdoaWNoIGFyZSBub3QgbXV0YXRlZCBidXQgYXJlIGludm9sdmVkIGluIGZ1c2lvbnMgb3IgU1ZzCmRhdGEkQWx0ZXJhdGlvbnMgPC0gZ3N1YiggIk5vbmUiLCAiQ04iLCBkYXRhJEFsdGVyYXRpb25zKQpkYXRhLnN1YiRBbHRlcmF0aW9ucyA8LSBnc3ViKCAiTm9uZSIsICJDTiIsIGRhdGEuc3ViJEFsdGVyYXRpb25zKQoKIyMjIyMgUHJlcGFyZSBkYXRhZnJhbWUgZm9yIG1hbmhhdHRhbmx5CiMjIyMjIEtlZXAgb25seSBnZW5lcyBmb3Igd2hpY2ggYm90aCBnZW5lcyBoYXZlIGdlbmUgc3ltYm9sIChhbmQgZ2Vub21pY3MgbG9jYXRpb24pIGF2YWlsYWJsZQpkYXRhIDwtIGRhdGFbIGRhdGEkR2VuZSAlaW4lIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3QiXV0kU1lNQk9MLCBdCm5hbWVzKGRhdGEpW21hdGNoKCJDTiIsIG5hbWVzKGRhdGEpKV0gPC0gIlAiCgojIyMjIyBNZXJnZSBnZW5lcyBnZW5vbWljIGNvb3JkaW5hdGVzIGluZm8gd2l0aCB0aGVpciBhbm5vdGF0aW9uIGFuZCBleHByZXNzaW9uIGRhdGEKZGF0YS5hbm5vdCA8LSBtZXJnZShkYXRhLCByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90Il1dLCBieS54ID0gIkdlbmUiLCBieS55ID0gIlNZTUJPTCIsIGFsbC54ID0gRkFMU0UpCmRhdGEuYW5ub3QkU0VRTkFNRSA8LSBhcy5udW1lcmljKGRhdGEuYW5ub3QkU0VRTkFNRSkKZGF0YS5hbm5vdCRHRU5FU0VRU1RBUlQgPC0gYXMubnVtZXJpYyhkYXRhLmFubm90JEdFTkVTRVFTVEFSVCkKZGF0YS5hbm5vdCA8LSBkYXRhLmFubm90WyAhaXMubmEoZGF0YS5hbm5vdCRTRVFOQU1FKSwgXQoKaWYgKCBucm93KGRhdGEuYW5ub3QpID4gMCApIHsKICAKICAjIyMjIyBHZXQgcGxvdCByZXN1bHRzIGZpcnN0IHRvIGV4dHJhY3QgeC1heGlzIGNvb3JkaW5hdGVkIHRvIGFubm90YXRlIGdlbmVzIG9mIGludGVyZXN0CiAgbWFuaGF0dGFuci5yZXMgPC0gbWFuaGF0dGFucih4ID0gZGF0YS5hbm5vdCwgY2hyID0gIlNFUU5BTUUiLCBicCA9ICJHRU5FU0VRU1RBUlQiLCBwID0gIlAiLCBzbnAgPSAiR2VuZSIsIGdlbmUgPSAiWl9zY29yZV9kaWZmIiwgYW5ub3RhdGlvbjEgPSAiUGVyY19kaWZmIiwgYW5ub3RhdGlvbjIgPSAiQWx0ZXJhdGlvbnMiLCBsb2dwID0gRkFMU0UpCiAgCiAgIyMjIyMgUmVzdHJpY3QgdGhlIHJlc3VsdHMgdG8gdGhlIGdlbmVzIG9mIGludGVyZXN0CiAgbWFuaGF0dGFuci5yZXMkZGF0YSA8LSBtYW5oYXR0YW5yLnJlcyRkYXRhWyBtYW5oYXR0YW5yLnJlcyRkYXRhJEdlbmUgJWluJSBkYXRhLnN1YiRHZW5lLCBdCiAgCiAgcCA8LSBtYW5oYXR0YW5seSh4ID0gZGF0YS5hbm5vdCwgY2hyID0gIlNFUU5BTUUiLCBicCA9ICJHRU5FU0VRU1RBUlQiLCBwID0gIlAiLCBzbnAgPSAiR2VuZSIsIGdlbmUgPSAiWl9zY29yZV9kaWZmIiwgYW5ub3RhdGlvbjEgPSAiUGVyY19kaWZmIiwgYW5ub3RhdGlvbjIgPSAiQWx0ZXJhdGlvbnMiLCBzdWdnZXN0aXZlbGluZSA9IGNuX3RvcCwgZ2Vub21ld2lkZWxpbmUgID0gY25fYm90dG9tLCBzdWdnZXN0aXZlbGluZV9jb2xvciA9ICJncmF5IiwgZ2Vub21ld2lkZWxpbmVfY29sb3IgPSAiZ3JheSIsIHlsYWIgPSAiQ04gdmFsdWUiLCBzaG93Z3JpZCA9IEZBTFNFLCB0aXRsZSA9ICIiLCBsb2dwID0gRkFMU0UpICU+JQogICAgCiAgICBhZGRfbWFya2Vycyh5ID0gbWFuaGF0dGFuci5yZXMkZGF0YSRQLCB4ID0gbWFuaGF0dGFuci5yZXMkZGF0YSRwb3MsIAogICAgICAgICAgICAgICAgbmFtZSA9IG1hbmhhdHRhbnIucmVzJGRhdGEkR2VuZSwKICAgICAgICAgICAgICAgIHRleHQgPSBwYXN0ZTAoIkdlbmU6ICIsIG1hbmhhdHRhbnIucmVzJGRhdGEkR2VuZSwgIlxuWl9zY29yZV9kaWZmOiAiLCBtYW5oYXR0YW5yLnJlcyRkYXRhJFpfc2NvcmVfZGlmZiwgIlxuUGVyY19kaWZmOiAiLCBtYW5oYXR0YW5yLnJlcyRkYXRhJFBlcmNfZGlmZiwgIlxuQWx0ZXJhdGlvbnM6ICIsIG1hbmhhdHRhbnIucmVzJGRhdGEkQWx0ZXJhdGlvbnMsICJcbmNocjogIiwgbWFuaGF0dGFuci5yZXMkZGF0YSRDSFIpLAogICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywKICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3Qoc2l6ZT0xMCwgc3ltYm9sPSJjaXJjbGUiKSwKICAgICAgICAgICAgICAgIGNvbG9yID0gbWFuaGF0dGFuci5yZXMkZGF0YSRHZW5lLAogICAgICAgICAgICAgICAgc2hvd2xlZ2VuZCA9IFRSVUUsCiAgICAgICAgICAgICAgICBsZWdlbmR0aXRsZT1UUlVFLCAKICAgICAgICAgICAgICAgIGluaGVyaXQgPSBGQUxTRSkgJT4lCiAgICAKICAgIGFkZF9hbm5vdGF0aW9ucyggZGF0YSA9IG1hbmhhdHRhbnIucmVzJGRhdGEsIHRleHQ9fkdlbmUsCiAgICAgICAgICAgICAgICAgICAgICB4PX5wb3MsIHhhbmNob3I9ImxlZnQiLAogICAgICAgICAgICAgICAgICAgICAgeT1+UCwgeWFuY2hvcj0idG9wIiwKICAgICAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KGNvbG9yID0gIkdyZXkiLCBzaXplID0gMTApLAogICAgICAgICAgICAgICAgICAgICAgbGVnZW5kdGl0bGU9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgIHNob3dhcnJvdz1GQUxTRSApCiAgCiAgIyMjIyMgQ3JlYXRlIGRpcmVjdG9yeSBmb3IgdGhlIHBsb3RzCiAgUGxvdERpciA8LSBwYXN0ZShyZXN1bHRzX2RpciwgImNuX2dlbm9taWNfdmlldyIsIHNlcCA9ICIvIikKICBpZiAoICFmaWxlLmV4aXN0cyhQbG90RGlyKSApIHsKICAgIGRpci5jcmVhdGUoUGxvdERpciwgcmVjdXJzaXZlPVRSVUUpCiAgfQogIAogICMjIyMjIFNhdmUgaW50ZXJhY3RpdmUgcGxvdCBhcyBodG1sIGZpbGUKICBzYXZlV2lkZ2V0Rml4KHAsIGZpbGUgPSBwYXN0ZShQbG90RGlyLCAiY25fZ2Vub21pY192aWV3Lmh0bWwiLCBzZXAgPSAiLyIpKQogIAogICMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQogIGlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQogIAp9IGVsc2UgewogIGNhdCgiTm9uZSBvZiB0aGUgZ2VuZXMgb2YgaW50ZXJlc3QgYXJlIGFmZmVjdGVkIGJ5IGNoYW5nZXMgaW4gQ04uIikKICBwIDwtIE5VTEwKfQoKcAoKIyMjIyMgRGV0YWNoIHBsb3RseSBwYWNrYWdlLiBPdGhlcndpc2UgaXQgY2xhc2hlcyB3aXRoIG90aGVyIGdyYXBoaWNzIGRldmljZXMKZGV0YWNoKCJwYWNrYWdlOm1hbmhhdHRhbmx5IiwgdW5sb2FkPUZBTFNFKQpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQpgYGAKCioqKgoKPGRldGFpbHM+CjxzdW1tYXJ5PkNOIGRhdGEgZGlzdHJpYnV0aW9uPC9zdW1tYXJ5PgoKYHIgaWYgKCBydW5QdXJwbGVDaHVuayApIHsgYygiICIpIH0gZWxzZSB7IGMoIkNOIGluZm9ybWF0aW9uIGZvciB0aGlzIHNhbXBsZSBpcyBOT1QgQVZBSUxBQkxFLiIpIH1gCgpgYGB7ciBjbl9kYXRhX2Rpc3RyaWJ1dGlvbl9wbG90LCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNCwgZXZhbCA9IHJ1blB1cnBsZUNodW5rIH0KIyMjIyMgR2VuZXJhdGUgYSBoaXN0b2dyYW0gaWxsdXN0cmF0aW5nIENOIGRhdGEgZGlzdHJpYnV0aW9uCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQpjbl9kaXN0X3Bsb3QKCiMjIyMjIENyZWF0ZSBkaXJlY3RvcnkgZm9yIHRoZSBwbG90cwpQbG90RGlyIDwtIHBhc3RlKHJlc3VsdHNfZGlyLCAiY25fZGlzdF9wbG90Iiwgc2VwID0gIi8iKQppZiAoICFmaWxlLmV4aXN0cyhQbG90RGlyKSApIHsKICBkaXIuY3JlYXRlKFBsb3REaXIsIHJlY3Vyc2l2ZT1UUlVFKQp9CgojIyMjIyBTYXZlIGludGVyYWN0aXZlIHBsb3QgYXMgaHRtbCBmaWxlCnNhdmVXaWRnZXRGaXgoY25fZGlzdF9wbG90LCBmaWxlID0gcGFzdGUoUGxvdERpciwgImNuX2Rpc3RfcGxvdC5odG1sIiwgc2VwID0gIi8iKSkKZGV0YWNoKCJwYWNrYWdlOnBsb3RseSIsIHVubG9hZD1GQUxTRSkKCiMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQppZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKYGBgCgo8L2RldGFpbHM+CgoqKioKCiMjIyAtIEV4cHJlc3Npb24gdnMgQ04gey50YWJzZXR9CgpTY2F0dGVycGxvdCBjb21wYXJpbmcgdGhlIHBlci1nZW5lIGRpZmZlcmVuY2UgaW4gKiptUk5BIGV4cHJlc3Npb24qKiBvZiBbQ2FuY2VyIGdlbmVzXSBiZXR3ZWVuIHBhdGllbnQncyBzYW1wbGUgYW5kIGNhbmNlciBpbmRpdmlkdWFscyAoKnktYXhpcyopLCBhbmQgKipDTiB2YWx1ZXMqKiAoKngtYXhpcyosIGZyb20gW1BVUlBMRV0oaHR0cHM6Ly9naXRodWIuY29tL2hhcnR3aWdtZWRpY2FsL2htZnRvb2xzL3RyZWUvbWFzdGVyL3B1cml0eS1wbG9pZHktZXN0aW1hdG9yKXt0YXJnZXQ9Il9ibGFuayJ9KS4KCiMjIyMgUGVyY2VudGlsZXMKCmBgYHtyIGNuX2V4cHJfZGF0YV9wbG90X3BlcmMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDQsIGV2YWwgPSBydW5QdXJwbGVDaHVua30KIyMjIyMgR2VuZXJhdGUgc2NhdHRlcnBsb3Qgd2l0aCBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyAoeS1heGlzKSwgQ04gdmFsdWVzICh4LWF4aXMpIGFuZCBtdXRhdGlvbiBzdGF0dXMgaW5mbyAoY29sb3VycykKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCmNuX2dlbmVzIDwtIGRhdGEuc3ViJEdlbmUKCmlmICggcnVuUGNnckNodW5rICYmIGxlbmd0aChjbl9nZW5lcykgPiAwICkgewogIG11dENOZXhwclBsb3QoZGF0YSA9IGRhdGEuc3ViLCBhbHRfZGF0YSA9IFRSVUUsIGNuX2JvdHRvbSA9IGNuX2JvdHRvbSwgY25fdG9wID0gY25fdG9wLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCB0eXBlID0gInBlcmMiLCByZXBvcnRfZGlyID0gcmVzdWx0c19kaXIpCiAgCn0gZWxzZSBpZiAoIGxlbmd0aChjbl9nZW5lcykgPiAwKSB7CiAgZGF0YS5zdWIgPC0gZGF0YS5zdWJbIGRhdGEuc3ViJEdlbmUgJWluJSBjbl9nZW5lcywgXQogIG11dENOZXhwclBsb3QoZGF0YSA9IGRhdGEuc3ViLCBhbHRfZGF0YSA9IEZBTFNFLCBjbl9ib3R0b20gPSBjbl9ib3R0b20sIGNuX3RvcCA9IGNuX3RvcCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgdHlwZSA9ICJwZXJjIiwgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQogIAp9IGVsc2UgewogIGNuX2dlbmVzIDwtIE5VTEwKICBjYXQoIk5vbmUgb2YgdGhlIGdlbmVzIG9mIGludGVyZXN0IGFyZSBhZmZlY3RlZCBieSBjaGFuZ2VzIGluIENOLiIpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKKioqCgojIyMjIFotc2NvcmVzCgpgYGB7ciBjbl9leHByX2RhdGFfcGxvdCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNCwgZXZhbCA9IHJ1blB1cnBsZUNodW5rfQojIyMjIyBHZW5lcmF0ZSBzY2F0dGVycGxvdCB3aXRoIHBlci1nZW5lIGV4cHJlc3Npb24gdmFsdWVzICh5LWF4aXMpLCBDTiB2YWx1ZXMgKHgtYXhpcykgYW5kIG11dGF0aW9uIHN0YXR1cyBpbmZvIChjb2xvdXJzKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGxvdGx5KSkKCmlmICggcnVuUGNnckNodW5rICYmIGxlbmd0aChjbl9nZW5lcykgPiAwICkgewogIGRhdGEuc3ViIDwtIGRhdGEuc3ViWyBkYXRhLnN1YiRHZW5lICVpbiUgY25fZ2VuZXMsIF0KICBtdXRDTmV4cHJQbG90KGRhdGEgPSBkYXRhLnN1YiwgYWx0X2RhdGEgPSBUUlVFLCBjbl9ib3R0b20gPSBjbl9ib3R0b20sIGNuX3RvcCA9IGNuX3RvcCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgdHlwZSA9ICJ6IiwgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQogIAp9IGVsc2UgaWYgKCBsZW5ndGgoY25fZ2VuZXMpID4gMCkgewogIGRhdGEuc3ViIDwtIGRhdGEuc3ViWyBkYXRhLnN1YiRHZW5lICVpbiUgY25fZ2VuZXMsIF0KICBtdXRDTmV4cHJQbG90KGRhdGEgPSBkYXRhLnN1YiwgYWx0X2RhdGEgPSBGQUxTRSwgY25fYm90dG9tID0gY25fYm90dG9tLCBjbl90b3AgPSBjbl90b3AsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIHR5cGUgPSAieiIsIHJlcG9ydF9kaXIgPSByZXN1bHRzX2RpcikKICAKfSBlbHNlIHsKICBjbl9nZW5lcyA8LSBOVUxMCiAgY2F0KCJOb25lIG9mIHRoZSBnZW5lcyBvZiBpbnRlcmVzdCBhcmUgYWZmZWN0ZWQgYnkgY2hhbmdlcyBpbiBDTi4iKQp9CgojIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQpgYGAKCioqKgoKIyMjIC0gU3VtbWFyeSB0YWJsZSB7LnRhYnNldH0KCk91dCBvZiB0aGUgYHIgaWYgKHJ1blB1cnBsZUNodW5rKSB7IGxlbmd0aChyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJleHByX211dF9jbl9kYXRhX2FsbCJdXVsgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YV9hbGwiXV0kQ04gPD0gY25fYm90dG9tIHwgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YV9hbGwiXV0kQ04gPj0gY25fdG9wLCBdWywxXSkgfSBlbHNlIHsgY2F0KCIwIikgfWAgZ2VuZXMgd2l0aGluIGdhaW5lZCBgciBpZiAoIHJ1blB1cnBsZUNodW5rICkgeyBwYXN0ZTAoIihDTiB2YWx1ZXMgPj0gIiwgY25fdG9wLCAiKSIpIH1gIG9yIGxvc3QgYHIgaWYgKCBydW5QdXJwbGVDaHVuayApIHsgcGFzdGUwKCIoQ04gdmFsdWVzID08ICIsIGNuX2JvdHRvbSwgIikiKSB9YCByZWdpb25zIGByIGlmIChydW5QdXJwbGVDaHVuaykgeyBsZW5ndGgocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YSJdXVssMV0pIH0gZWxzZSB7IGxlbmd0aChOVUxMKSB9YCBhcmUgW0NhbmNlciBnZW5lc10uIFRoZSBleHByZXNzaW9uIG9mICoqYHIgaWYgKHJ1blB1cnBsZUNodW5rKSB7IGxlbmd0aCh3aGljaChyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJleHByX211dF9jbl9kYXRhIl1dJEdlbmUgJWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKSB9IGVsc2UgeyBsZW5ndGgoTlVMTCkgfWAqKiBvZiB0aGVzZSBnZW5lcyB3YXMgcmVsaWFibHkgbWVhc3VyZWQgaW4gcGF0aWVudCdzIHNhbXBsZS4gVGhlIHJlbWFpbmluZyBgciBpZiAocnVuUHVycGxlQ2h1bmspIHsgbGVuZ3RoKHdoaWNoKGNuX2dlbmVzICUhaW4lIHJvd25hbWVzKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dKSkpIH0gZWxzZSB7IGxlbmd0aChOVUxMKSB9YCBnZW5lcyBhcmUgZWl0aGVyIG5vdCBleHByZXNzZWQgb3IgdGhlaXIgZXhwcmVzc2lvbiBsZXZlbCBpcyB0b28gbG93IHRvIGJlIGRldGVjdGVkIChpbmRpY2F0ZWQgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzKS4KCiMjIyMgR2FpbnMgey50YWJzZXR9CgpUYWJsZSBzdW1tYXJpc2luZyB0aGUgKiptUk5BIGV4cHJlc3Npb24qKiB2YWx1ZXMgaW4gY2FuY2VyIGFuZCBwYXRpZW50IHNhbXBsZXMgZm9yIGdlbmVzIHdpdGggKipDTioqIHZhbHVlcyA+PSBgciBpZiAoIHJ1blB1cnBsZUNodW5rICkgeyBjbl90b3AgfSBlbHNlIHsgYygiKE5BKSIpIH1gICgqKmdhaW5zKiopLCBiYXNlZCBvbiBwYXRpZW50J3MgZ2Vub21pYyBkYXRhIChmcm9tIFtQVVJQTEVdKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXJ0d2lnbWVkaWNhbC9obWZ0b29scy90cmVlL21hc3Rlci9wdXJpdHktcGxvaWR5LWVzdGltYXRvcil7dGFyZ2V0PSJfYmxhbmsifSksIGFuZCBtdXRhdGlvbiBzdGF0dXMgaWYgYXZhaWxhYmxlIChmcm9tIFtQQ0dSXShodHRwczovL2dpdGh1Yi5jb20vc2lndmVuL3BjZ3Ipe3RhcmdldD0iX2JsYW5rIn0pLgoKIyMjIyMgUGVyY2VudGlsZXMKCmBgYHtyIGNuX2V4cHJfZGF0YV90YWJsZV9nYWluc19wZXJjLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBydW5QdXJwbGVDaHVua30KIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBDTiB2YWx1ZXMgYW5kIG11dGF0aW9uIHN0YXR1cyBpbmZvIChjb2xvdXJzKQojIyMjIyBLZWVwIG9ubHkgZ2VuZXMgd2l0aGluIENOIGdhaW5zCmNuX2RhdGEgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YSJdXQpjbl9kYXRhIDwtIGNuX2RhdGFbIGNuX2RhdGEkQ04gPj0gY25fdG9wLCBdCmNuX2RhdGEgPC0gY25fZGF0YVssICJDTiIsIGRyb3A9RkFMU0VdCmdlbmVzX2dhaW5zID0gYXMuY2hhcmFjdGVyKGNuX2dlbmVzWyBjbl9nZW5lcyAlaW4lIHJvd25hbWVzKGNuX2RhdGEpIF0pCgojIyMjIyBEZWFsIHdpdGggbm8gZ2VuZXMKaWYgKCBsZW5ndGgoZ2VuZXNfZ2FpbnMpID09IDAgKSB7CiAgZ2VuZXNfZ2FpbnMgPC0gTlVMTAogIGdlbmVzX2dhaW5zX25vIDwtIDAKfSBlbHNlIGlmICggbGVuZ3RoKGdlbmVzX2dhaW5zKSA+IHBhcmFtcyR0b3BfZ2VuZXMgKSB7CiAgZ2VuZXNfZ2FpbnNfbm8gPC0gcGFyYW1zJHRvcF9nZW5lcwp9IGVsc2UgewogIGdlbmVzX2dhaW5zX25vIDwtIGxlbmd0aChnZW5lc19nYWlucykKfQoKIyMjIyMgR2V0IGV4cHJlc3Npb24gZGF0YQpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dCgppZiAoIHJ1blBjZ3JDaHVuayAmJiBydW5QdXJwbGVDaHVuayApIHsKICBjbl9leHByX2dlbmVzLmV4cHIuZ2FpbnMucGVyYyA8LSBleHByVGFibGUoIGdlbmVzID0gZ2VuZXNfZ2FpbnMsIGRhdGEgPSBkYXRhLCBjbl9kYXRhID0gY25fZGF0YSwgY25fZGVjcmVhc2UgPSBUUlVFLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG11dF9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1sicGNnciJdXVssIGMoIlNZTUJPTCIsICJUSUVSIiwgIkNPTlNFUVVFTkNFIiwgIlZBUklBTlRfQ0xBU1MiLCAiQUZfVFVNT1IiLCAiR0VOT01JQ19DSEFOR0UiLCAiUFJPVEVJTl9DSEFOR0UiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXVssIGMoIk9uY29nZW5lIiwgIlRTRyIsICJGdXNpb24iLCAiR2VybWxpbmUiKSBdLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInBlcmMiLCBzY2FsaW5nID0gc2NhbGluZykKICAKIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBhbmQgQ04gdmFsdWVzCn0gZWxzZSBpZiAoIHJ1blB1cnBsZUNodW5rICkgewogIGNuX2V4cHJfZ2VuZXMuZXhwci5nYWlucy5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lc19nYWlucywgZGF0YSA9IGRhdGEsIGNuX2RhdGEgPSBjbl9kYXRhLCBjbl9kZWNyZWFzZSA9IFRSVUUsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGNhbmNlcl9nZW5lcyA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dWywgYygiT25jb2dlbmUiLCAiVFNHIiwgIkZ1c2lvbiIsICJHZXJtbGluZSIpIF0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAicGVyYyIsIHNjYWxpbmcgPSBzY2FsaW5nKQp9CiAgCiMjIyMjIFByZXNlbnQgdGhlIGV4cHJlc3Npb24sIENOIGFuZCBtdXRhdGlvbiBkYXRhIHN1bW1hcnkgdGFibGUKY25fZXhwcl9nZW5lcy5leHByLmdhaW5zLnBlcmNbWzFdXQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9Y25fZXhwcl9nZW5lcy5leHByLmdhaW5zLnBlcmNbWzFdXSwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJjbl9leHByX2dlbmVzLmV4cHIuZ2FpbnMucGVyYy5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPlJFRDwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipoaWdoIGV4cHJlc3Npb24qKiAocGVyY2VudGlsZSkgdmFsdWVzIGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6IzAwMDBmZiI+QkxVRTwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipsb3cgZXhwcmVzc2lvbioqIChwZXJjZW50aWxlKSB2YWx1ZXMgaW4gaW5kaXZpZHVhbCBzYW1wbGUgZ3JvdXAuIFRoZSA8c3BhbiBzdHlsZT0iY29sb3I6IzgwODA4MCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW5kaWNhdGUgZ2VuZXMgd2l0aCAqKm5vL2xvdyBleHByZXNzaW9uKiouIFRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW4gaWxsdXN0cmF0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwZXJjZW50aWxlcyBpbiBwYXRpZW50IHNhbXBsZSBhbmQgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQgZm9yIGVhY2ggZ2VuZS4gVGhlICoqQ04gdmFsdWVzKiogYmFzZWQgb24gcGF0aWVudCdzIGdlbm9taWMgZGF0YSBhcmUgcHJlc2VudGVkIGluICpQYXRpZW50IChDTikqIGNvbHVtbiB3aXRoIGEgaG9yaXpvbnRhbCBibHVlIGJhciBpbmRpY2F0aW5nIHRoZSBDTiB2YWx1ZSBvZiBlYWNoIGdlbmUgaW4gdGhlIGNvbnRleHQgb2Ygb3RoZXIgZ2VuZXMuIElmIG11dGF0aW9uIGRhdGEgaXMgYXZhaWxiYWxlLCB0aGVuIHRoZSB2YXJpYW50c+KAmSB0aWVyLCBjb25zZXF1ZW5jZSwgY2xhc3MgYW5kIHR1bW91ciBhbGxlbGUgZnJldXFuZWN5IChBRiksIGFzIHdlbGwgYXMgZ2Vub21pYyBhbmQgcHJvdGVpbiBjaGFuZ2UgYXJlIGFsc28gcHJvdmlkZWQgb24gcmlnaHQtaGFuZCBzaWRlIGJhc2VkIG9uIGluZm9ybWF0aW9uIGZyb20gW1BDR1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaWd2ZW4vcGNncil7dGFyZ2V0PSJfYmxhbmsifSByZXBvcnQgKHNpbWlsYXIgdG8gW011dGF0ZWQgZ2VuZXNdIHNlY3Rpb24pLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKlBhdGllbnQgKENOKSoqIGFuZCB0aGVuIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1ucy4gKkNOKiAtIGNvcHktbnVtYmVyCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCmByIGlmICggcnVuUHVycGxlQ2h1bmsgJiYgbGVuZ3RoKGdlbmVzX2dhaW5zKSA+IDIwMDAgKSB7IGMocGFzdGUwKCI8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj5OT1RFPC9zcGFuPiwgdGhlIHRhYmxlIHdhcyB0cnVuY2F0ZWQgdG8gMjAwMCBlbnRyaWVzLiIpKSB9IGVsc2UgeyBjYXQoIiIpIH1gCgoqKioKCiMjIyMjIFotc2NvcmVzCgpgYGB7ciBjbl9leHByX2RhdGFfdGFibGVfZ2FpbnMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIGV2YWwgPSBydW5QdXJwbGVDaHVua30KIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBDTiB2YWx1ZXMgYW5kIG11dGF0aW9uIHN0YXR1cyBpbmZvIChjb2xvdXJzKQppZiAoIHJ1blBjZ3JDaHVuayAmJiBydW5QdXJwbGVDaHVuayApIHsKICBjbl9leHByX2dlbmVzLmV4cHIuZ2FpbnMueiA8LSBleHByVGFibGUoIGdlbmVzID0gZ2VuZXNfZ2FpbnMsIGRhdGEgPSBkYXRhLCBjbl9kYXRhID0gY25fZGF0YSwgY25fZGVjcmVhc2UgPSBUUlVFLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG11dF9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1sicGNnciJdXVssIGMoIlNZTUJPTCIsICJUSUVSIiwgIkNPTlNFUVVFTkNFIiwgIlZBUklBTlRfQ0xBU1MiLCAiQUZfVFVNT1IiLCAiR0VOT01JQ19DSEFOR0UiLCAiUFJPVEVJTl9DSEFOR0UiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXVssIGMoIk9uY29nZW5lIiwgIlRTRyIsICJGdXNpb24iLCAiR2VybWxpbmUiKSBdLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInoiLCBzY2FsaW5nID0gc2NhbGluZykKICAKIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBhbmQgQ04gdmFsdWVzCn0gZWxzZSBpZiAoIHJ1blB1cnBsZUNodW5rICkgewogIGNuX2V4cHJfZ2VuZXMuZXhwci5nYWlucy56IDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lc19nYWlucywgZGF0YSA9IGRhdGEsIGNuX2RhdGEgPSBjbl9kYXRhLCBjbl9kZWNyZWFzZSA9IFRSVUUsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgY2FuY2VyX2dlbmVzID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV1bLCBjKCJPbmNvZ2VuZSIsICJUU0ciLCAiRnVzaW9uIiwgIkdlcm1saW5lIikgXSwgZXh0X2xpbmtzID0gVFJVRSwgdHlwZSA9ICJ6Iiwgc2NhbGluZyA9IHNjYWxpbmcpCn0KICAKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiwgQ04gYW5kIG11dGF0aW9uIGRhdGEgc3VtbWFyeSB0YWJsZQpjbl9leHByX2dlbmVzLmV4cHIuZ2FpbnMueltbMV1dCgojIyMjIyBTYXZlIHRoZSBleHByZXNzaW9uIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1jbl9leHByX2dlbmVzLmV4cHIuZ2FpbnMueltbMV1dLCBmaWxlPXBhc3RlKGV4cHJUYWJsZURpciwgImNuX2V4cHJfZ2VuZXMuZXhwci5nYWlucy56Lmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShjbl9leHByX2dlbmVzLmV4cHIuZ2FpbnMueiwgY25fZGF0YSkKYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmhpZ2ggZXhwcmVzc2lvbioqIChaLXNjb3JlKSB2YWx1ZXMgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojMDAwMGZmIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKFotc2NvcmUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojODA4MDgwIj5CTEFOSzwvc3Bhbj4gY2VsbHMgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbmRpY2F0ZSBnZW5lcyB3aXRoICoqbm8vbG93IGV4cHJlc3Npb24qKi4gVGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbiBpbGx1c3RyYXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFotc2NvcmVzIGluIHBhdGllbnQgc2FtcGxlIGFuZCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydCBmb3IgZWFjaCBnZW5lLiBUaGUgKipDTiB2YWx1ZXMqKiBiYXNlZCBvbiBwYXRpZW50J3MgZ2Vub21pYyBkYXRhIGFyZSBwcmVzZW50ZWQgaW4gKlBhdGllbnQgKENOKSogY29sdW1uIHdpdGggYSBob3Jpem9udGFsIGJsdWUgYmFyIGluZGljYXRpbmcgdGhlIENOIHZhbHVlIG9mIGVhY2ggZ2VuZSBpbiB0aGUgY29udGV4dCBvZiBvdGhlciBnZW5lcy4gSWYgbXV0YXRpb24gZGF0YSBpcyBhdmFpbGJhbGUsIHRoZW4gdGhlIHZhcmlhbnRz4oCZIHRpZXIsIGNvbnNlcXVlbmNlLCBjbGFzcyBhbmQgdHVtb3VyIGFsbGVsZSBmcmV1cW5lY3kgKEFGKSwgYXMgd2VsbCBhcyBnZW5vbWljIGFuZCBwcm90ZWluIGNoYW5nZSBhcmUgYWxzbyBwcm92aWRlZCBvbiByaWdodC1oYW5kIHNpZGUgYmFzZWQgb24gaW5mb3JtYXRpb24gZnJvbSBbUENHUl0oaHR0cHM6Ly9naXRodWIuY29tL3NpZ3Zlbi9wY2dyKXt0YXJnZXQ9Il9ibGFuayJ9IHJlcG9ydCAoc2ltaWxhciB0byBbTXV0YXRlZCBnZW5lc10gc2VjdGlvbikuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqUGF0aWVudCAoQ04pKiogYW5kIHRoZW4gYnkgKipkZWNyZWFzaW5nKiogYWJzb2x1dGUgdmFsdWVzIGluIHRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW5zLiAqQ04qIC0gY29weS1udW1iZXIKCjwvZm9udD4KPC9kZXRhaWxzPgoKYHIgaWYgKCBydW5QdXJwbGVDaHVuayAmJiBsZW5ndGgoZ2VuZXNfZ2FpbnMpID4gMjAwMCApIHsgYyhwYXN0ZTAoIjxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPk5PVEU8L3NwYW4+LCB0aGUgdGFibGUgd2FzIHRydW5jYXRlZCB0byAyMDAwIGVudHJpZXMuIikpIH0gZWxzZSB7IGNhdCgiIikgfWAKCioqKgoKIyMjIyBMb3NzZXMgey50YWJzZXR9CgpUYWJsZSBzdW1tYXJpc2luZyB0aGUgKiptUk5BIGV4cHJlc3Npb24qKiB2YWx1ZXMgaW4gY2FuY2VyIGFuZCBwYXRpZW50IHNhbXBsZXMgZm9yIGdlbmVzIHdpdGggKipDTioqIHZhbHVlcyA9PCBgciBpZiAoIHJ1blB1cnBsZUNodW5rICkgeyBjbl9ib3R0b20gfSBlbHNlIHsgYygiKE5BKSIpIH1gICgqKmxvc3NlcyoqKSwgYmFzZWQgb24gcGF0aWVudCdzIGdlbm9taWMgZGF0YSAoZnJvbSBbUFVSUExFXShodHRwczovL2dpdGh1Yi5jb20vaGFydHdpZ21lZGljYWwvaG1mdG9vbHMvdHJlZS9tYXN0ZXIvcHVyaXR5LXBsb2lkeS1lc3RpbWF0b3Ipe3RhcmdldD0iX2JsYW5rIn0pLCBhbmQgbXV0YXRpb24gc3RhdHVzIGlmIGF2YWlsYWJsZSAoZnJvbSBbUENHUl0oaHR0cHM6Ly9naXRodWIuY29tL3NpZ3Zlbi9wY2dyKXt0YXJnZXQ9Il9ibGFuayJ9KS4KCiMjIyMjIFBlcmNlbnRpbGVzCgpgYGB7ciBjbl9leHByX2RhdGFfdGFibGVfbG9zc2VzX3BlcmMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blB1cnBsZUNodW5rfQojIyMjIyBHZW5lcmF0ZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUgZm9yIHBlci1nZW5lIGV4cHJlc3Npb24gdmFsdWVzIENOIHZhbHVlcyBhbmQgbXV0YXRpb24gc3RhdHVzIGluZm8gKGNvbG91cnMpCiMjIyMjIEtlZXAgb25seSBnZW5lcyB3aXRoaW4gQ04gbG9zc2VzCmNuX2RhdGEgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZXhwcl9tdXRfY25fZGF0YSJdXQpjbl9kYXRhIDwtIGNuX2RhdGFbIGNuX2RhdGEkQ04gPD0gY25fYm90dG9tLCBdCmNuX2RhdGEgPC0gY25fZGF0YVssICJDTiIsIGRyb3A9RkFMU0VdCmdlbmVzX2xvc3NlcyA9IGFzLmNoYXJhY3Rlcihjbl9nZW5lc1sgY25fZ2VuZXMgJWluJSByb3duYW1lcyhjbl9kYXRhKSBdKQoKIyMjIyMgRGVhbCB3aXRoIG5vIGdlbmVzIG9yIHdoZW4gbW9yZSB0aGFuIDEwIGdlbmVzIGFyZSBvZiBpbnRlcmVzdAppZiAoIGxlbmd0aChnZW5lc19sb3NzZXMpID09IDAgKSB7CiAgZ2VuZXNfbG9zc2VzIDwtIE5VTEwKICBnZW5lc19sb3NzZXNfbm8gPC0gMAp9IGVsc2UgaWYgKCBsZW5ndGgoZ2VuZXNfbG9zc2VzKSA+IHBhcmFtcyR0b3BfZ2VuZXMgKSB7CiAgZ2VuZXNfbG9zc2VzX25vIDwtIHBhcmFtcyR0b3BfZ2VuZXMKfSBlbHNlIHsKICBnZW5lc19sb3NzZXNfbm8gPC0gbGVuZ3RoKGdlbmVzX2xvc3NlcykKfQogIAppZiAoIGdlbmVzX2dhaW5zX25vICsgZ2VuZXNfbG9zc2VzX25vID4gcGFyYW1zJHRvcF9nZW5lcyApIHsKICBsaW1pdF9nZW5lcyA8LSBUUlVFCn0gZWxzZSB7CiAgbGltaXRfZ2VuZXMgPC0gRkFMU0UKfQoKIyMjIyMgR2V0IGV4cHJlc3Npb24gZGF0YQpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dCgppZiAoIHJ1blBjZ3JDaHVuayAmJiBydW5QdXJwbGVDaHVuayApIHsKICBjbl9leHByX2dlbmVzLmV4cHIubG9zc2VzLnBlcmMgPC0gZXhwclRhYmxlKCBnZW5lcyA9IGdlbmVzX2xvc3NlcywgZGF0YSA9IGRhdGEsIGNuX2RhdGEgPSBjbl9kYXRhLCBjbl9kZWNyZWFzZSA9IEZBTFNFLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG11dF9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1sicGNnciJdXVssIGMoIlNZTUJPTCIsICJUSUVSIiwgIkNPTlNFUVVFTkNFIiwgIlZBUklBTlRfQ0xBU1MiLCAiQUZfVFVNT1IiLCAiR0VOT01JQ19DSEFOR0UiLCAiUFJPVEVJTl9DSEFOR0UiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXVssIGMoIk9uY29nZW5lIiwgIlRTRyIsICJGdXNpb24iLCAiR2VybWxpbmUiKSBdLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInBlcmMiLCBzY2FsaW5nID0gc2NhbGluZykKICAKIyMjIyMgR2VuZXJhdGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlIGZvciBwZXItZ2VuZSBleHByZXNzaW9uIHZhbHVlcyBhbmQgQ04gdmFsdWVzCn0gZWxzZSBpZiAoIHJ1blB1cnBsZUNodW5rICkgewogIGNuX2V4cHJfZ2VuZXMuZXhwci5sb3NzZXMucGVyYyA8LSBleHByVGFibGUoIGdlbmVzID0gZ2VuZXNfbG9zc2VzLCBkYXRhID0gZGF0YSwgY25fZGF0YSA9IGNuX2RhdGEsIGNuX2RlY3JlYXNlID0gRkFMU0UsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGNhbmNlcl9nZW5lcyA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dWywgYygiT25jb2dlbmUiLCAiVFNHIiwgIkZ1c2lvbiIsICJHZXJtbGluZSIpIF0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAicGVyYyIsIHNjYWxpbmcgPSBzY2FsaW5nKQp9CiAgCiMjIyMjIFByZXNlbnQgdGhlIGV4cHJlc3Npb24sIENOIGFuZCBtdXRhdGlvbiBkYXRhIHN1bW1hcnkgdGFibGUKY25fZXhwcl9nZW5lcy5leHByLmxvc3Nlcy5wZXJjW1sxXV0KCiMjIyMjIFNhdmUgdGhlIGV4cHJlc3Npb24gdGFibGUgYXMgaHRtbCBmaWxlCmlmICggcGFyYW1zJHNhdmVfdGFibGVzICkgewogIHNhdmVXaWRnZXRGaXgod2lkZ2V0PWNuX2V4cHJfZ2VuZXMuZXhwci5sb3NzZXMucGVyY1tbMV1dLCBmaWxlPXBhc3RlKGV4cHJUYWJsZURpciwgImNuX2V4cHJfZ2VuZXMuZXhwci5sb3NzZXMucGVyYy5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPlJFRDwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipoaWdoIGV4cHJlc3Npb24qKiAocGVyY2VudGlsZSkgdmFsdWVzIGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6IzAwMDBmZiI+QkxVRTwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipsb3cgZXhwcmVzc2lvbioqIChwZXJjZW50aWxlKSB2YWx1ZXMgaW4gaW5kaXZpZHVhbCBzYW1wbGUgZ3JvdXAuIFRoZSA8c3BhbiBzdHlsZT0iY29sb3I6IzgwODA4MCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW5kaWNhdGUgZ2VuZXMgd2l0aCAqKm5vL2xvdyBleHByZXNzaW9uKiouIFRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW4gaWxsdXN0cmF0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBwZXJjZW50aWxlcyBpbiBwYXRpZW50IHNhbXBsZSBhbmQgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQgZm9yIGVhY2ggZ2VuZS4gVGhlICoqQ04gdmFsdWVzKiogYmFzZWQgb24gcGF0aWVudCdzIGdlbm9taWMgZGF0YSBhcmUgcHJlc2VudGVkIGluICpQYXRpZW50IChDTikqIGNvbHVtbiB3aXRoIGEgaG9yaXpvbnRhbCBibHVlIGJhciBpbmRpY2F0aW5nIHRoZSBDTiB2YWx1ZSBvZiBlYWNoIGdlbmUgaW4gdGhlIGNvbnRleHQgb2Ygb3RoZXIgZ2VuZXMuIElmIG11dGF0aW9uIGRhdGEgaXMgYXZhaWxiYWxlLCB0aGVuIHRoZSB2YXJpYW50c+KAmSB0aWVyLCBjb25zZXF1ZW5jZSwgY2xhc3MgYW5kIHR1bW91ciBhbGxlbGUgZnJldXFuZWN5IChBRiksIGFzIHdlbGwgYXMgZ2Vub21pYyBhbmQgcHJvdGVpbiBjaGFuZ2UgYXJlIGFsc28gcHJvdmlkZWQgb24gcmlnaHQtaGFuZCBzaWRlIGJhc2VkIG9uIGluZm9ybWF0aW9uIGZyb20gW1BDR1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaWd2ZW4vcGNncil7dGFyZ2V0PSJfYmxhbmsifSByZXBvcnQgKHNpbWlsYXIgdG8gW011dGF0ZWQgZ2VuZXNdIHNlY3Rpb24pLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKlBhdGllbnQgKENOKSoqIGFuZCB0aGVuIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1ucy4gKkNOKiAtIGNvcHktbnVtYmVyCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCmByIGlmICggcnVuUHVycGxlQ2h1bmsgJiYgbGVuZ3RoKGdlbmVzX2xvc3NlcykgPiAyMDAwICkgeyBjKHBhc3RlMCgiPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+Tk9URTwvc3Bhbj4sIHRoZSB0YWJsZSB3YXMgdHJ1bmNhdGVkIHRvIDIwMDAgZW50cmllcy4iKSkgfSBlbHNlIHsgY2F0KCIiKSB9YAoKKioqCgojIyMjIyBaLXNjb3JlcwoKYGBge3IgY25fZXhwcl9kYXRhX3RhYmxlX2xvc3NlcywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcnVuUHVycGxlQ2h1bmt9CiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgcGVyLWdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgQ04gdmFsdWVzIGFuZCBtdXRhdGlvbiBzdGF0dXMgaW5mbyAoY29sb3VycykKaWYgKCBydW5QY2dyQ2h1bmsgJiYgcnVuUHVycGxlQ2h1bmsgKSB7CiAgY25fZXhwcl9nZW5lcy5leHByLmxvc3Nlcy56IDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lc19sb3NzZXMsIGRhdGEgPSBkYXRhLCBjbl9kYXRhID0gY25fZGF0YSwgY25fZGVjcmVhc2UgPSBGQUxTRSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgZ2VuZXNfYW5ub3QgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXVssIGMoIlNZTUJPTCIsICJFTlNFTUJMIildLCBtdXRfYW5ub3QgPSByZWZfZ2VuZXMubGlzdFtbInBjZ3IiXV1bLCBjKCJTWU1CT0wiLCAiVElFUiIsICJDT05TRVFVRU5DRSIsICJWQVJJQU5UX0NMQVNTIiwgIkFGX1RVTU9SIiwgIkdFTk9NSUNfQ0hBTkdFIiwgIlBST1RFSU5fQ0hBTkdFIildLCBvbmNva2JfYW5ub3QgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX29uY29rYiJdXSwgY2FuY2VyX2dlbmVzID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV1bLCBjKCJPbmNvZ2VuZSIsICJUU0ciLCAiRnVzaW9uIiwgIkdlcm1saW5lIikgXSwgZXh0X2xpbmtzID0gVFJVRSwgdHlwZSA9ICJ6Iiwgc2NhbGluZyA9IHNjYWxpbmcpCiAgCiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgcGVyLWdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgYW5kIENOIHZhbHVlcwp9IGVsc2UgaWYgKCBydW5QdXJwbGVDaHVuayApIHsKICBjbl9leHByX2dlbmVzLmV4cHIubG9zc2VzLnogPC0gZXhwclRhYmxlKCBnZW5lcyA9IGdlbmVzX2xvc3NlcywgZGF0YSA9IGRhdGEsIGNuX2RhdGEgPSBjbl9kYXRhLCBjbl9kZWNyZWFzZSA9IEZBTFNFLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBnZW5lc19hbm5vdCA9IHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImdlbmVfYW5ub3RfYWxsIl1dWywgYygiU1lNQk9MIiwgIkVOU0VNQkwiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBjYW5jZXJfZ2VuZXMgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXVssIGMoIk9uY29nZW5lIiwgIlRTRyIsICJGdXNpb24iLCAiR2VybWxpbmUiKSBdLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInoiLCBzY2FsaW5nID0gc2NhbGluZykKfQogIAojIyMjIyBQcmVzZW50IHRoZSBleHByZXNzaW9uLCBDTiBhbmQgbXV0YXRpb24gZGF0YSBzdW1tYXJ5IHRhYmxlCmNuX2V4cHJfZ2VuZXMuZXhwci5sb3NzZXMueltbMV1dCgojIyMjIyBTYXZlIHRoZSBleHByZXNzaW9uIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1jbl9leHByX2dlbmVzLmV4cHIubG9zc2VzLnpbWzFdXSwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJjbl9leHByX2dlbmVzLmV4cHIubG9zc2VzLnouaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlIGFuZCByZXR1cm4gb3V0cHV0CnJtKGNuX2RhdGEsIGNuX2V4cHJfZ2VuZXMuZXhwci5sb3NzZXMueikKYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmhpZ2ggZXhwcmVzc2lvbioqIChaLXNjb3JlKSB2YWx1ZXMgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojMDAwMGZmIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKFotc2NvcmUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojODA4MDgwIj5CTEFOSzwvc3Bhbj4gY2VsbHMgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbmRpY2F0ZSBnZW5lcyB3aXRoICoqbm8vbG93IGV4cHJlc3Npb24qKi4gVGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbiBpbGx1c3RyYXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIFotc2NvcmVzIGluIHBhdGllbnQgc2FtcGxlIGFuZCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydCBmb3IgZWFjaCBnZW5lLiBUaGUgKipDTiB2YWx1ZXMqKiBiYXNlZCBvbiBwYXRpZW50J3MgZ2Vub21pYyBkYXRhIGFyZSBwcmVzZW50ZWQgaW4gKlBhdGllbnQgKENOKSogY29sdW1uIHdpdGggYSBob3Jpem9udGFsIGJsdWUgYmFyIGluZGljYXRpbmcgdGhlIENOIHZhbHVlIG9mIGVhY2ggZ2VuZSBpbiB0aGUgY29udGV4dCBvZiBvdGhlciBnZW5lcy4gSWYgbXV0YXRpb24gZGF0YSBpcyBhdmFpbGJhbGUsIHRoZW4gdGhlIHZhcmlhbnRz4oCZIHRpZXIsIGNvbnNlcXVlbmNlLCBjbGFzcyBhbmQgdHVtb3VyIGFsbGVsZSBmcmV1cW5lY3kgKEFGKSwgYXMgd2VsbCBhcyBnZW5vbWljIGFuZCBwcm90ZWluIGNoYW5nZSBhcmUgYWxzbyBwcm92aWRlZCBvbiByaWdodC1oYW5kIHNpZGUgYmFzZWQgb24gaW5mb3JtYXRpb24gZnJvbSBbUENHUl0oaHR0cHM6Ly9naXRodWIuY29tL3NpZ3Zlbi9wY2dyKXt0YXJnZXQ9Il9ibGFuayJ9IHJlcG9ydCAoc2ltaWxhciB0byBbTXV0YXRlZCBnZW5lc10gc2VjdGlvbikuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqUGF0aWVudCAoQ04pKiogYW5kIHRoZW4gYnkgKipkZWNyZWFzaW5nKiogYWJzb2x1dGUgdmFsdWVzIGluIHRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW5zLiAqQ04qIC0gY29weS1udW1iZXIKCjwvZm9udD4KPC9kZXRhaWxzPgoKYHIgaWYgKCBydW5QdXJwbGVDaHVuayAmJiBsZW5ndGgoZ2VuZXNfbG9zc2VzKSA+IDIwMDAgKSB7IGMocGFzdGUwKCI8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj5OT1RFPC9zcGFuPiwgdGhlIHRhYmxlIHdhcyB0cnVuY2F0ZWQgdG8gMjAwMCBlbnRyaWVzLiIpKSB9IGVsc2UgeyBjYXQoIiIpIH1gCgoqKioKCiMjIyAtIEV4cHJlc3Npb24gcHJvZmlsZXMgey50YWJzZXR9CgpgciBpZiAoIGV4aXN0cygibGltaXRfZ2VuZXMiKSAmJiBleGlzdHMoImdlbmVzX2dhaW5zX25vIikgKSB7IGlmICggbGltaXRfZ2VuZXMgKSB7IGMocGFzdGUwKCJFeHByZXNzaW9uIHByb2ZpbGVzIGZvciAiLCBnZW5lc19nYWluc19ubywgIiBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0ICgqKmdhaW5zKiopIGFuZCAiLCBnZW5lc19sb3NzZXNfbm8sICIgZ2VuZXMgd2l0aCB0aGUgbG93ZXN0ICgqKmxvc3NlcyoqKSBDTiB2YWx1ZXMgYW5kIHRoZSBncmVhdGVzdCBkaWZmZXJlbmNlIGluIG1STkEgZXhwcmVzc2lvbiAocGVyY2VudGlsZSkgdmFsdWVzIGJldHdlZW4gcGF0aWVudCdzIHNhbXBsZSBhbmQgdGhlIGF2ZXJhZ2UgbVJOQSBleHByZXNzaW9uIGluIHNhbXBsZXMgZnJvbSBjYW5jZXIgcGF0aWVudHMuIikpIH0gZWxzZSB7IGNhdCgiICIpIH19YAoKIyMjIyBHYWlucyB7LnRhYnNldH0KCmBgYHtyIGNkZl9wbG90X2NuX2V4cHJfZ2FpbnMsIGVjaG89RkFMU0UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIGV2YWwgPSBydW5QdXJwbGVDaHVuaywgcmVzdWx0cz0iYXNpcyJ9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShwbG90bHkpKQojIyMjIyBHZW5lcmF0ZSBlbXBpcmljYWwgY3VtdWxhdGl2ZSBkaXN0cmlidXRpb24gZnVuY3Rpb24gKEVDREYpIHBsb3QgaWxsdXN0cmF0aW5nIG1STkEgZXhwcmVzc2lvbiBsZXZlbCBmb3IgdGhlIGdlbmVzIG9mIGludGVyZXN0IGluIHRoZSBjb250ZXh0IG9mIHRoZSBvdmVyYWxsIG1STkEgZXhwcmVzc2lvbiBkaXN0cmlidXRpb24Kb3V0cHV0X2NkZiA8LSBsaXN0KCkKb3V0cHV0X2NvdW50cyA8LSBsaXN0KCkKb3V0cHV0X2RlbnNpdHkgPC0gbGlzdCgpCmdlbmVzIDwtIGNuX2V4cHJfZ2VuZXMuZXhwci5nYWlucy5wZXJjW1syXV0kU1lNQk9MCgojIyMjIyBGb3IgZWFjaCBnZW5lIGdlbmVyYXRlICgxKSBDREYgcGxvdCBhbmQgYWRkIGJveHBsb3QgYmVsb3cgdG8gc2hvdyB0aGUgZGF0YSB2YXJpYW5jZSBmb3Igc2VsZWN0ZWQgZ2VuZSBpbiBpbmRpdmlkdWFsIGdyb3VwcywgKDIpIGJhci1wbG90IG9mIHJlYWQgY291bnQgZGF0YSBhY3Jvc3MgYWxsIHNhbXBsZXMgYW5kICgzKSBkZW5zaXR5IHBsb3QgdG8gZGVtb25zdHJhdGUgZXhwcmVzc2lvbiBkaXN0cmlidXRpb24gaW4gaW52ZXN0aWdhdGVkIHNhbXBsZSAKZm9yKCBpIGluIDE6Z2VuZXNfZ2FpbnNfbm8gKSB7CiAgaWYgKCBnZW5lc19nYWluc19ubyA+IDAgJiYgZ2VuZXNbaV0gJWluJSByb3duYW1lcyhkYXRhKSApIHsKICAgIAogICAgIyMjIyMgQ0RGIHBsb3QKICAgIG91dHB1dF9jZGZbW2ldXSA8LSBjZGZQbG90KGdlbmUgPSBnZW5lc1tpXSwgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGFkZEJveFBsb3QgPSBUUlVFLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQogICAgCiAgICAjIyMjIyBCYXItcGxvdCBvZiByZWFkIGNvdW50cwogICAgIyMjIyMgRmlyc3QgbWFwIHRoZSBnZW5lIHN5bWJvbCB0byBFbnNtZWJsIElEICh1c2VkIGluIHRoZSBjb3VudHMgZGF0YSkKICAgIGdlbmVzLkVOU0VNQkwgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV0kRU5TRU1CTFsgcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV0kU1lNQk9MID09ICBnZW5lc1tpXSBdCiAgICAKICAgIG91dHB1dF9jb3VudHNbW2ldXSA8LSBiYXJQbG90KGdlbmUgPSBnZW5lcy5FTlNFTUJMLCBkYXRhID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siY29tYmluZWRfZGF0YSJdXSwgeV90aXRsZSA9ICJDb3VudHMiLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwICkKICAgIAogICAgIyMjIyMgRGVuc2l0eSBwbG90IC0gZXhwcmVzc2lvbiBkaXN0cmlidXRpb24KICAgIG91dHB1dF9kZW5zaXR5W1tpXV0gPC0gZGVuc2l0eVBsb3QoZ2VuZSA9IGdlbmVzW2ldLCBkYXRhID0gZGF0YSwgbWFpbl90aXRsZT0gIiIsIHhfdGl0bGUgPSAiWi1zY29yZSIsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZGlzdHJpYnV0aW9ucyA9IGMoIm5vcm1hbCIsICJiaW1vZGFsIiksIHNjYWxpbmcgPSBzY2FsaW5nKSAKICB9Cn0KCiMjIyMjIE5vdyBvbmNlIHRoZSBwbG90cyBhcmUgcmVhZHkgc2hvdyB0aGVtIGluIHNlcGFyYXRlIHRhYnMKaWYgKCBnZW5lc19nYWluc19ubyAhPSAwICkgewogIGZvciggaSBpbiAxOmdlbmVzX2dhaW5zX25vICl7CiAgICBpZiAoIGdlbmVzW2ldICVpbiUgcm93bmFtZXMoZGF0YSkgKSB7CiAgICAgIGNhdCgiXG4jIyMjIyAiLCBnZW5lc1tpXSwgIlxuIikKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NkZltbaV1dKSRodG1sKQogICAgICBjYXQoIlxuPGRldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPHN1bW1hcnk+UGxvdCBsZWdlbmQ8L3N1bW1hcnk+XG4iKQogICAgICBjYXQoIjxmb250IHNpemU9XCIyXCI+XG4iKQogICAgICBjYXQocGFzdGUwKCIqKlRvcCBwYW5lbCoqOiBkaXN0cmlidXRpb24gb2YgcGVyY2VudGlsZSB2YWx1ZXMgKCp5LWF4aXMqKSBhcyBhIGZ1bmN0aW9uIG9mIGV4cHJlc3Npb24gbGV2ZWxzIChaLXNjb3JlcywgKngtYXhpcyopIGZvciAqIiwgZ2VuZXNbaV0sICIqIGluIHBhdGllbnQncyBzYW1wbGUgKCpibGFjayBkb3QqKSBhbmQgb3RoZXIgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQocykgKG1lZGlhbiB2YWx1ZShzKSkuXG5cbiIpKQogICAgICBjYXQocGFzdGUwKCIqKkJvdHRvbSBwYW5lbCoqOiBib3gtcGxvdCBwcmVzZW50aW5nIGV4cHJlc3Npb24gbGV2ZWwgKFotc2NvcmUpIG9mICoiLCBnZW5lc1tpXSwgIiogaW4gcGF0aWVudCdzIHNhbXBsZSAoKmJsYWNrIGRvdCopIGFuZCBpdHMgZXhwcmVzc2lvbiBsZXZlbHMgb2JzZXJ2ZWQgYWNyb3NzIHNhbXBsZXMgZnJvbSBvdGhlciByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydChzKS5cbiIpKQogICAgICBjYXQoIlxuPC9mb250PlxuIikKICAgICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5SZWFkIGNvdW50czwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdChyZW5kZXJUYWdzKG91dHB1dF9jb3VudHNbW2ldXSkkaHRtbCkKICAgICAgY2F0KCI8Zm9udCBzaXplPVwiMlwiPlxuIikKICAgICAgY2F0KHBhc3RlMCgiQmFyLXBsb3QgaWxsdXN0cmF0aW5nIHJlYWQgY291bnRzIGZvciAqIiwgZ2VuZXNbaV0sICIqIGFjcm9zcyBhbGwgc2FtcGxlcy4gVGhlICoiLCBnZW5lc1tpXSwgIiogcmVhZCBjb3VudCBpbiBwYXRpZW50J3Mgc2FtcGxlIGlzIGluZGljYXRlZCBieSAqYmxhY2sgYmFyKi5cbiIpKQogICAgICBjYXQoIlxuPC9mb250PlxuIikKICAgICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5FeHByZXNzaW9uIGRpc3RyaWJ1dGlvbiBwYXR0ZXJuczwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdChyZW5kZXJUYWdzKG91dHB1dF9kZW5zaXR5W1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIlBsb3QgaWxsdXN0cmF0aW5nIGRpc3RyaWJ1dGlvbiBvZiBleHByZXNzaW9uIGxldmVscyAoWi1zY29yZXMpIG9mICoiLCBnZW5lc1tpXSwgIiogKm9ic2VydmVkKiBhY3Jvc3MgYWxsIHNhbXBsZXMgYWxvbmcgd2l0aCBzaW11bGF0ZWQgKm5vcm1hbCogYW5kICpiaW1vZGFsKiBkaXN0cmlidXRpb25zLiBUaGUgKiIsIGdlbmVzW2ldLCAiKiBleHByZXNzaW9uIGxldmVsIG9ic2VydmVkIGluIHBhdGllbnQncyBzYW1wbGUgaXMgaW5kaWNhdGVkIGJ5ICpibGFjayBkb3QqIGluIGVhY2ggZGlzdHJpYnV0aW9uLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbioqKlxuIikKICAgIH0gZWxzZSB7CiAgICAgIGNhdCgiXG4jIyMjICIsIGdlbmVzW2ldLCAiXG4iKQogICAgICBjYXQoIlxuPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+Tk9URTwvc3Bhbj4sIGV4cHJlc3Npb24gZGF0YSBpcyBub3QgYXZhaWxhYmxlIGZvciB0aGF0IGdlbmUuXG4iKQogICAgICBjYXQoIlxuKioqXG4iKQogICAgfQogICAgIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CiAgICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKICB9Cn0gZWxzZSB7CiAgY2F0KCJcbk5vIGFsdGVyYXRpb25zIHdlcmUgcmVwb3J0ZWQuXG4iKQogICBjYXQoIlxuKioqXG4iKQp9CgojIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGxpc3QgPSBscyhwYXR0ZXJuPSdeb3V0cHV0KicpKQpgYGAKCioqKgoKIyMjIyBMb3NzZXMgey50YWJzZXR9CgpgYGB7ciBjZGZfcGxvdF9jbl9leHByX2xvc3NlcywgZWNobz1GQUxTRSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMywgZXZhbCA9IHJ1blB1cnBsZUNodW5rLCByZXN1bHRzPSJhc2lzIn0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCiMjIyMjIEdlbmVyYXRlIGVtcGlyaWNhbCBjdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiAoRUNERikgcGxvdCBpbGx1c3RyYXRpbmcgbVJOQSBleHByZXNzaW9uIGxldmVsIGZvciB0aGUgZ2VuZXMgb2YgaW50ZXJlc3QgaW4gdGhlIGNvbnRleHQgb2YgdGhlIG92ZXJhbGwgbVJOQSBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbgpvdXRwdXRfY2RmIDwtIGxpc3QoKQpvdXRwdXRfY291bnRzIDwtIGxpc3QoKQpvdXRwdXRfZGVuc2l0eSA8LSBsaXN0KCkKZ2VuZXMgPC0gY25fZXhwcl9nZW5lcy5leHByLmxvc3Nlcy5wZXJjW1syXV0kU1lNQk9MCgojIyMjIyBGb3IgZWFjaCBnZW5lIGdlbmVyYXRlICgxKSBDREYgcGxvdCBhbmQgYWRkIGJveHBsb3QgYmVsb3cgdG8gc2hvdyB0aGUgZGF0YSB2YXJpYW5jZSBmb3Igc2VsZWN0ZWQgZ2VuZSBpbiBpbmRpdmlkdWFsIGdyb3VwcywgKDIpIGJhci1wbG90IG9mIHJlYWQgY291bnQgZGF0YSBhY3Jvc3MgYWxsIHNhbXBsZXMgYW5kICgzKSBkZW5zaXR5IHBsb3QgdG8gZGVtb25zdHJhdGUgZXhwcmVzc2lvbiBkaXN0cmlidXRpb24gaW4gaW52ZXN0aWdhdGVkIHNhbXBsZSAKZm9yKCBpIGluIDE6Z2VuZXNfbG9zc2VzX25vICkgewogIGlmICggZ2VuZXNfbG9zc2VzX25vID4gMCAmJiBnZW5lc1tpXSAlaW4lIHJvd25hbWVzKGRhdGEpICkgewogICAgCiAgICAjIyMjIyBDREYgcGxvdAogICAgb3V0cHV0X2NkZltbaV1dIDwtIGNkZlBsb3QoZ2VuZSA9IGdlbmVzW2ldLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgYWRkQm94UGxvdCA9IFRSVUUsIHNjYWxpbmcgPSBzY2FsaW5nLCByZXBvcnRfZGlyID0gcmVzdWx0c19kaXIpCiAgICAKICAgICMjIyMjIEJhci1wbG90IG9mIHJlYWQgY291bnRzCiAgICAjIyMjIyBGaXJzdCBtYXAgdGhlIGdlbmUgc3ltYm9sIHRvIEVuc21lYmwgSUQgKHVzZWQgaW4gdGhlIGNvdW50cyBkYXRhKQogICAgZ2VuZXMuRU5TRU1CTCA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRFTlNFTUJMWyByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXSRTWU1CT0wgPT0gIGdlbmVzW2ldIF0KICAgIAogICAgb3V0cHV0X2NvdW50c1tbaV1dIDwtIGJhclBsb3QoZ2VuZSA9IGdlbmVzLkVOU0VNQkwsIGRhdGEgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJjb21iaW5lZF9kYXRhIl1dLCB5X3RpdGxlID0gIkNvdW50cyIsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAgKQogICAgCiAgICAjIyMjIyBEZW5zaXR5IHBsb3QgLSBleHByZXNzaW9uIGRpc3RyaWJ1dGlvbgogICAgb3V0cHV0X2RlbnNpdHlbW2ldXSA8LSBkZW5zaXR5UGxvdChnZW5lID0gZ2VuZXNbaV0sIGRhdGEgPSBkYXRhLCBtYWluX3RpdGxlPSAiIiwgeF90aXRsZSA9ICJaLXNjb3JlIiwgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBkaXN0cmlidXRpb25zID0gYygibm9ybWFsIiwgImJpbW9kYWwiKSwgc2NhbGluZyA9IHNjYWxpbmcpCiAgfQp9CgojIyMjIyBOb3cgb25jZSB0aGUgcGxvdHMgYXJlIHJlYWR5IHNob3cgdGhlbSBpbiBzZXBhcmF0ZSB0YWJzCmlmICggZ2VuZXNfbG9zc2VzX25vICE9IDAgKSB7CiAgZm9yKCBpIGluIDE6Z2VuZXNfbG9zc2VzX25vICl7CiAgICBpZiAoIGdlbmVzW2ldICVpbiUgcm93bmFtZXMoZGF0YSkgKSB7CiAgICAgIGNhdCgiXG4jIyMjIyAiLCBnZW5lc1tpXSwgIlxuIikKICAgICAgY2F0KHJlbmRlclRhZ3Mob3V0cHV0X2NkZltbaV1dKSRodG1sKQogICAgICBjYXQoIlxuPGRldGFpbHM+XG4iKQogICAgICBjYXQoIlxuPHN1bW1hcnk+UGxvdCBsZWdlbmQ8L3N1bW1hcnk+XG4iKQogICAgICBjYXQoIjxmb250IHNpemU9XCIyXCI+XG4iKQogICAgICBjYXQocGFzdGUwKCIqKlRvcCBwYW5lbCoqOiBkaXN0cmlidXRpb24gb2YgcGVyY2VudGlsZSB2YWx1ZXMgKCp5LWF4aXMqKSBhcyBhIGZ1bmN0aW9uIG9mIGV4cHJlc3Npb24gbGV2ZWxzIChaLXNjb3JlcywgKngtYXhpcyopIGZvciAqIiwgZ2VuZXNbaV0sICIqIGluIHBhdGllbnQncyBzYW1wbGUgKCpibGFjayBkb3QqKSBhbmQgb3RoZXIgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQocykgKG1lZGlhbiB2YWx1ZShzKSkuXG5cbiIpKQogICAgICBjYXQocGFzdGUwKCIqKkJvdHRvbSBwYW5lbCoqOiBib3gtcGxvdCBwcmVzZW50aW5nIGV4cHJlc3Npb24gbGV2ZWwgKFotc2NvcmUpIG9mICoiLCBnZW5lc1tpXSwgIiogaW4gcGF0aWVudCdzIHNhbXBsZSAoKmJsYWNrIGRvdCopIGFuZCBpdHMgZXhwcmVzc2lvbiBsZXZlbHMgb2JzZXJ2ZWQgYWNyb3NzIHNhbXBsZXMgZnJvbSBvdGhlciByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydChzKS5cbiIpKQogICAgICBjYXQoIlxuPC9mb250PlxuIikKICAgICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5SZWFkIGNvdW50czwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdChyZW5kZXJUYWdzKG91dHB1dF9jb3VudHNbW2ldXSkkaHRtbCkKICAgICAgY2F0KCI8Zm9udCBzaXplPVwiMlwiPlxuIikKICAgICAgY2F0KHBhc3RlMCgiQmFyLXBsb3QgaWxsdXN0cmF0aW5nIHJlYWQgY291bnRzIGZvciAqIiwgZ2VuZXNbaV0sICIqIGFjcm9zcyBhbGwgc2FtcGxlcy4gVGhlICoiLCBnZW5lc1tpXSwgIiogcmVhZCBjb3VudCBpbiBwYXRpZW50J3Mgc2FtcGxlIGlzIGluZGljYXRlZCBieSAqYmxhY2sgYmFyKi5cbiIpKQogICAgICBjYXQoIlxuPC9mb250PlxuIikKICAgICAgY2F0KCJcbjwvZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48ZGV0YWlscz5cbiIpCiAgICAgIGNhdCgiXG48c3VtbWFyeT5FeHByZXNzaW9uIGRpc3RyaWJ1dGlvbiBwYXR0ZXJuczwvc3VtbWFyeT5cbiIpCiAgICAgIGNhdChyZW5kZXJUYWdzKG91dHB1dF9kZW5zaXR5W1tpXV0pJGh0bWwpCiAgICAgIGNhdCgiPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCiAgICAgIGNhdChwYXN0ZTAoIlBsb3QgaWxsdXN0cmF0aW5nIGRpc3RyaWJ1dGlvbiBvZiBleHByZXNzaW9uIGxldmVscyAoWi1zY29yZXMpIG9mICoiLCBnZW5lc1tpXSwgIiogKm9ic2VydmVkKiBhY3Jvc3MgYWxsIHNhbXBsZXMgYWxvbmcgd2l0aCBzaW11bGF0ZWQgKm5vcm1hbCogYW5kICpiaW1vZGFsKiBkaXN0cmlidXRpb25zLiBUaGUgKiIsIGdlbmVzW2ldLCAiKiBleHByZXNzaW9uIGxldmVsIG9ic2VydmVkIGluIHBhdGllbnQncyBzYW1wbGUgaXMgaW5kaWNhdGVkIGJ5ICpibGFjayBkb3QqIGluIGVhY2ggZGlzdHJpYnV0aW9uLlxuIikpCiAgICAgIGNhdCgiXG48L2ZvbnQ+XG4iKQogICAgICBjYXQoIlxuPC9kZXRhaWxzPlxuIikKICAgICAgY2F0KCJcbioqKlxuIikKICAgIH0gZWxzZSB7CiAgICAgIGNhdCgiXG4jIyMjICIsIGdlbmVzW2ldLCAiXG4iKQogICAgICBjYXQoIlxuPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+Tk9URTwvc3Bhbj4sIGV4cHJlc3Npb24gZGF0YSBpcyBub3QgYXZhaWxhYmxlIGZvciB0aGF0IGdlbmUuXG4iKQogICAgICBjYXQoIlxuKioqXG4iKQogICAgfQogIH0KICAjIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKICBpZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKICAKfSBlbHNlIHsKICBjYXQoIlxuTm8gYWx0ZXJhdGlvbnMgd2VyZSByZXBvcnRlZC5cbiIpCiAgIGNhdCgiXG4qKipcbiIpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0obGlzdCA9IGxzKHBhdHRlcm49J15vdXRwdXQqJykpCnJtKGxpbWl0X2dlbmVzKQpgYGAKCmByIGlmICggIXJ1blB1cnBsZUNodW5rICkgeyBjKCIqKioiKSB9IGVsc2UgeyBjKCIgIikgfWAKCiMjIEltbXVuZSBtYXJrZXJzCgpTZWN0aW9uIHByZXNlbnRpbmcgZXhwcmVzc2lvbiBsZXZlbHMgb2YgaW1tdW5lIG1hcmtlcnMgdG8gYXNzZXNzIHByZS1leGlzdGluZyBhbnRpLWNhbmNlciBpbW11bml0eSBhbmQgbGlrZWxpaG9vZCBvZiByZXNwb25zZSB0byBpbW11bm90aGVyYXB5LiBUaGVpciBtUk5BIGV4cHJlc3Npb24gbGV2ZWxzIGFyZSBwcmVzZW50ZWQgaW4gcGF0aWVudCdzIHNhbXBsZSBhbG9uZyB0aGVpciBhdmVyYWdlIG1STkEgZXhwcmVzc2lvbiBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIGNvaG9ydHMuCgpPdXQgb2YgdGhlIGByIGxlbmd0aCh1bmlxdWUodW5saXN0KHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzJFNZTUJPTCkpKWAgaW1tdW5lIG1hcmtlcnMgdGhlIGV4cHJlc3Npb24gb2YgKipgciBsZW5ndGgod2hpY2gocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5lX21hcmtlcnMkU1lNQk9MICVpbiUgcm93bmFtZXMocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pKSlgKiogd2FzIHJlbGlhYmx5IG1lYXN1cmVkIGluIHBhdGllbnQncyBzYW1wbGUuIFRoZSByZW1haW5pbmcgYHIgbGVuZ3RoKHdoaWNoKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVuZV9tYXJrZXJzJFNZTUJPTCAlIWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKWAgZ2VuZXMgYXJlIGVpdGhlciBub3QgZXhwcmVzc2VkIG9yIHRoZWlyIGV4cHJlc3Npb24gbGV2ZWwgaXMgdG9vIGxvdyB0byBiZSBkZXRlY3RlZCAoaW5kaWNhdGVkIGluIDxzcGFuIHN0eWxlPSJjb2xvcjojODA4MDgwIj5CTEFOSzwvc3Bhbj4gY2VsbHMgd2l0aCBtaXNzaW5nIHZhbHVlcykuCgojIyMgLSBTdW1tYXJ5IHRhYmxlIHsudGFic2V0fQoKIyMjIyBQZXJjZW50aWxlcwoKYGBge3IgaW1tdW5lX2dlbmVzX3RhYmxlX3BlcmMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyMjIyMgVXBkYXRlIE15U1FMIGNvbW1lbmQgdG8gcG9wdWxhdGUgUk5BLXNlcSBkYXRhIHBvcnRhbApteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGUsICIsSW1tdW5lIG1hcmtlcnMiKQpteXNxbF9wb3B1bGF0ZV91cGRhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlX3VwZGF0ZSwgIixJbW11bmUgbWFya2VycyIpCgojIyMjIyBHZW5lcmF0ZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUgZm9yIGNhbmNlciBnZW5lcyBmcm9tIE9uY29LQiBhbmQgVU1DQ1IgKGh0dHBzOi8vZ2l0aHViLmNvbS92bGFkc2F2ZWxpZXYvTkdTX1V0aWxzL2Jsb2IvbWFzdGVyL25nc191dGlscy9yZWZlcmVuY2VfZGF0YS9rZXlfZ2VuZXMvdW1jY3JfY2FuY2VyX2dlbmVzLjIwMTktMDMtMjAudHN2KQp0YXJnZXRzIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dCmdlbmVzIDwtIHVuaXF1ZSh1bmxpc3QocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5lX21hcmtlcnMkU1lNQk9MKSkKCiMjIyMjIERlYWwgd2l0aCBubyBnZW5lcyBvciB3aGVuIG1vcmUgdGhhbiAxMCBnZW5lcyBhcmUgb2YgaW50ZXJlc3QKaWYgKCBsZW5ndGgoZ2VuZXMpID09IDAgKSB7CiAgZ2VuZXMgPC0gTlVMTAp9CgppbW11bmVfZ2VuZXMuZXhwci5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIsICJJbW11bmVfQ3ljbGVfUm9sZSIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAicGVyYyIsIHNjYWxpbmcgPSBzY2FsaW5nKVtbMV1dCgojIyMjIyBQcmVzZW50IHRoZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUKaW1tdW5lX2dlbmVzLmV4cHIucGVyYwoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9aW1tdW5lX2dlbmVzLmV4cHIucGVyYywgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJpbW11bmVfZ2VuZXMuZXhwci5wZXJjLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShpbW11bmVfZ2VuZXMuZXhwci5wZXJjKQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojZmYwMDAwIj5SRUQ8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqaGlnaCBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiMwMDAwZmYiPkJMVUU8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqbG93IGV4cHJlc3Npb24qKiAocGVyY2VudGlsZSkgdmFsdWVzIGluIGluZGl2aWR1YWwgc2FtcGxlIGdyb3VwLiBUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluZGljYXRlIGdlbmVzIHdpdGggKipuby9sb3cgZXhwcmVzc2lvbioqLiBUaGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uIGlsbHVzdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gcGVyY2VudGlsZXMgaW4gcGF0aWVudCBzYW1wbGUgYW5kIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0IGZvciBlYWNoIGltbXVuZSBtYXJrZXIuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uLgoKPC9mb250Pgo8L2RldGFpbHM+CgoqKioKCiMjIyMgWi1zY29yZXMKCmBgYHtyIGltbXVuZV9nZW5lc190YWJsZSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBHZW5lcmF0ZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUgZm9yIGNhbmNlciBnZW5lcyBmcm9tIE9uY29LQiBhbmQgVU1DQ1IgKGh0dHBzOi8vZ2l0aHViLmNvbS92bGFkc2F2ZWxpZXYvTkdTX1V0aWxzL2Jsb2IvbWFzdGVyL25nc191dGlscy9yZWZlcmVuY2VfZGF0YS9rZXlfZ2VuZXMvdW1jY3JfY2FuY2VyX2dlbmVzLjIwMTktMDMtMjAudHN2KQppbW11bmVfZ2VuZXMuZXhwci56IDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIsICJJbW11bmVfQ3ljbGVfUm9sZSIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAieiIsIHNjYWxpbmcgPSBzY2FsaW5nKVtbMV1dCgojIyMjIyBQcmVzZW50IHRoZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUKaW1tdW5lX2dlbmVzLmV4cHIuegoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9aW1tdW5lX2dlbmVzLmV4cHIueiwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJpbW11bmVfZ2VuZXMuZXhwci56Lmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShpbW11bmVfZ2VuZXMuZXhwci56KQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojZmYwMDAwIj5SRUQ8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqaGlnaCBleHByZXNzaW9uKiogKFotc2NvcmUpIHZhbHVlcyBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiMwMDAwZmYiPkJMVUU8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqbG93IGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGluIGluZGl2aWR1YWwgc2FtcGxlIGdyb3VwLiBUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluZGljYXRlIGdlbmVzIHdpdGggKipuby9sb3cgZXhwcmVzc2lvbioqLiBUaGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uIGlsbHVzdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gWi1zY29yZXMgaW4gcGF0aWVudCBzYW1wbGUgYW5kIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0IGZvciBlYWNoIGltbXVuZSBtYXJrZXIuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uLgoKPC9mb250Pgo8L2RldGFpbHM+CgoqKioKCiMjIyAtIEV4cHJlc3Npb24gb3ZlcnZpZXcgey50YWJzZXR9CgpPdmVydmlldyBvZiBpbW11bmUgbWFya2VycyBleHByZXNzaW9uIHByb2ZpbGVzIGluIHBhdGllbnQncyBzYW1wbGUgYW5kIGluIHNhbXBsZXMgZnJvbSBjYW5jZXIgcGF0aWVudHMuCgojIyMjIFBlcmNlbnRpbGVzCgpgYGB7ciBnbGFuY2VfZXhwcl9wbG90X2ltbXVuZV9nZW5lc19wZXJjLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSAzfQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGxvdGx5KSkKCiMjIyMjIEdlbmVyYXRlIG92ZXJ2aWV3IGJveHBsb3QKaWYgKCAhaXMubnVsbChnZW5lcykgKSB7CiAgZ2xhbmNlRXhwclBsb3QoZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGhleGNvZGUgPSAiaW1tdW5lX2dlbmVzIiwgdHlwZSA9ICJwZXJjIiwgc29ydCA9ICJhbHBoYWJldGljYWxseSIsIHNjYWxpbmcgPSBzY2FsaW5nLCByZXBvcnRfZGlyID0gcmVzdWx0c19kaXIpCn0gZWxzZSB7CiAgY2F0KCJcbk5vIGV4cHJlc3Npb24gZGF0YSBpcyBhdmFpbGFibGUgZm9yIGltbXVuZSBtYXJrZXJzIVxuIikKfQoKIyMjIyMgRGV0YWNoIHBsb3RseSBwYWNrYWdlLiBPdGhlcndpc2UgaXQgY2xhc2hlcyB3aXRoIG90aGVyIGdyYXBoaWNzIGRldmljZXMKZGV0YWNoKCJwYWNrYWdlOnBsb3RseSIsIHVubG9hZD1GQUxTRSkKCiMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQppZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+UGxvdCBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIGluZGl2aWR1YWwgYm94KGVzKSByZXByZXNlbnQgdGhlIGByIGlmICggaW50X2NhbmNlcl9ncm91cCA9PSBjb21wX2NhbmNlcl9ncm91cCApIHsgcGFzdGUwKGludF9jYW5jZXJfZ3JvdXAsICIgYW5kIikgfSBlbHNlIHsgY2F0KCIiKSB9YCBgciBleHRfY2FuY2VyX2dyb3VwYCBgciBpZiAoICFpcy5udWxsKGFkZF9jYW5jZXJfZ3JvdXApICkgeyBwYXN0ZTAoImFuZCAiLCBhZGRfY2FuY2VyX2dyb3VwKSB9IGVsc2UgeyBjYXQoIiIpIH1gIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0KHMpLCBhbmQgdGhlICoqQkxBQ0sqKiBkb3RzIGluZGljYXRlIGV4cHJlc3Npb24gKHBlcmNlbnRpbGUpIHZhbHVlcyBmb3IgZWFjaCBnZW5lIGluIHRoZSBwYXRpZW50IHNhbXBsZS4gR2VuZXMgYXJlIG9yZGVyZWQgICoqYWxwaGFiZXRpY2FsbHkqKi4KCjwvZm9udD4KPC9kZXRhaWxzPgoKKioqCgojIyMjIFotc2NvcmVzCgpgYGB7ciBnbGFuY2VfZXhwcl9wbG90X2ltbXVuZV9nZW5lcywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gM30Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCgojIyMjIyBHZW5lcmF0ZSBvdmVydmlldyBib3hwbG90CmlmICggIWlzLm51bGwoZ2VuZXMpICkgewogIGdsYW5jZUV4cHJQbG90KGdlbmVzID0gZ2VuZXMsIGRhdGEgPSBkYXRhLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBoZXhjb2RlID0gImltbXVuZV9nZW5lcyIsIHR5cGUgPSAieiIsIHNvcnQgPSAiYWxwaGFiZXRpY2FsbHkiLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQp9IGVsc2UgewogIGNhdCgiXG5ObyBleHByZXNzaW9uIGRhdGEgaXMgYXZhaWxhYmxlIGZvciBpbW11bmUgbWFya2VycyFcbiIpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlBsb3QgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSBpbmRpdmlkdWFsIGJveChlcykgcmVwcmVzZW50IHRoZSBgciBpZiAoIGludF9jYW5jZXJfZ3JvdXAgPT0gY29tcF9jYW5jZXJfZ3JvdXAgKSB7IHBhc3RlMChpbnRfY2FuY2VyX2dyb3VwLCAiIGFuZCIpIH0gZWxzZSB7IGNhdCgiIikgfWAgYHIgZXh0X2NhbmNlcl9ncm91cGAgYHIgaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyX2dyb3VwKSApIHsgcGFzdGUwKCJhbmQgIiwgYWRkX2NhbmNlcl9ncm91cCkgfSBlbHNlIHsgY2F0KCIiKSB9YCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydChzKSwgYW5kIHRoZSAqKkJMQUNLKiogZG90cyBpbmRpY2F0ZSBleHByZXNzaW9uIChaLXNjb3JlKSB2YWx1ZXMgZm9yIGVhY2ggZ2VuZSBpbiB0aGUgcGF0aWVudCBzYW1wbGUuIEdlbmVzIGFyZSBvcmRlcmVkICAqKmFscGhhYmV0aWNhbGx5KiouCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCioqKgoKYHIgaWYgKCBwYXJhbXMkaW1tdW5vZ3JhbSApIHsgYygiIyMjIC0gSW1tdW5vZ3JhbSB7LnRhYnNldH0iKSB9YAoKYHIgaWYgKCBwYXJhbXMkaW1tdW5vZ3JhbSApIHsgYygiVmlzdWFsaXNhdGlvbiBvZiBnZW5lcmFsIGFuZCBsb2NhbCBjYW5jZXIgaW1tdW5pdHkgc3RhdHVzIHVzaW5nICoqY2FuY2VyIGltbXVub2dyYW0qKiBmb3IgdGhlIFtjYW5jZXItaW1tdW5pdHkgY3ljbGVdKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3RvcGljcy9tZWRpY2luZS1hbmQtZGVudGlzdHJ5L3R1bW9yLWltbXVuaXR5KXt0YXJnZXQ9XCJfYmxhbmtcIn0gKENJQyksIGEgY29uY2VwdCBvZiBpbnRlZ3JhdGVkIGltbXVuZSBiaW9tYXJrZXJzIHNjb3Jpbmcgc3lzdGVtIHByb3Bvc2VkIGJ5IFtCbGFuayBldCBhbC5dKGh0dHBzOi8vc2NpZW5jZS5zY2llbmNlbWFnLm9yZy9jb250ZW50LzM1Mi82Mjg2LzY1OC5zdW1tYXJ5KXt0YXJnZXQ9XCJfYmxhbmtcIn0iKSB9YAoKYHIgaWYgKCBwYXJhbXMkaW1tdW5vZ3JhbSApIHsgYygiIyMjIyBQbG90IikgfWAKCmBgYHtyIGltbXVub2dyYW1fcGxvdCwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA2LCBmaWcuaGVpZ2h0ID0gNiwgZXZhbCA9IHBhcmFtcyRpbW11bm9ncmFtfQojIyMjIyBHZW5lcmF0ZSBzcGlkZXIgd2ViIHBsb3QgdG8gcHJlc2VudCB0aGUgcGF0aWVudCBjYW5jZXIgaW1tdW5pdHkgc3RhdHVzLiBGb3IgbW9yZSBpbmZvIGFib3V0IGltbXVub2dyYW0gc2VlIHRoZSBmb2xsb3dpbmcgcGFwZXJzCiMgaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMTU1NjA4NjQxNzMwMDA4NAojIGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzE1NTYwODY0MTczMDIxMjUKIyBodHRwczovL3d3dy5ldXJvcGVhbnVyb2xvZ3kuY29tL2FydGljbGUvUzAzMDItMjgzOCgxOCkzMDY4NS03L2Z1bGx0ZXh0P3Jzcz15ZXMKIyMjIyMgTk9URTogY3VycmVudGx5LCB0aGUgbWVhbiBleHByZXNzaW9uIChaLXNjb3JlKSB2YWx1ZXMgb2YgZ2VuZXMgZnJvbSBlYWNoIG9mIHRoZSA3IENJQyBzdGVwcyBhcmUgcHJlc2VudGVkIHJhdGhlciB0aGFuIHRoZSBub3JtYWxpemVkIGVucmljaG1lbnQgc2NvcmVzIChORVMpIGZyb20gR1NFQSBhbmFseXNpcyBwZXJmb3JtZWQgZm9yIGVhY2ggZ2VuZXNldCAoQ0lDIHN0ZXApCgojIyMjIyBQcmVzZXQgY2FuY2VyIGltbXVuaXR5IHN0YXR1cyBmb3IgdGhlIHBhdGllbnQgdXNpbmcgd2ViLXBsb3QKd2VicGxvdChhcy5kYXRhLmZyYW1lKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0uZGYpLCBkYXRhLnJvdyA9IG5jb2woZGF0YSksIG1haW4gPSAiIiwgYWRkID0gRkFMU0UsIGNvbCA9ICJibGFjayIpCgojIyMjIyBOb3cgYWRkIGRhdGEgZm9yIHNhbXBsZXMgd2l0aCBzcGVjaWZpYyBpbW11bm9ncmFtIHBhdHRlcm5zLCBlLmcuIFQtY2VsbOKAk3JpY2gsIFQtY2VsbOKAk3Bvb3IsIFQtY2VsbOKAk2ludGVybWVkaWF0ZS4uLgojd2VicGxvdChhcy5kYXRhLmZyYW1lKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0uZGYpLCBkYXRhLnJvdyA9IDUsIG1haW4gPSAiIiwgYWRkID0gVFJVRSwgY29sID0gInBvd2RlcmJsdWUiLCBsdHkgPSA1KQojd2VicGxvdChhcy5kYXRhLmZyYW1lKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0uZGYpLCBkYXRhLnJvdyA9IDE1NiwgbWFpbiA9ICIiLCBhZGQgPSBUUlVFLCBjb2wgPSAiZm9yZXN0Z3JlZW4iLCBsdHkgPSA1KQojd2VicGxvdChhcy5kYXRhLmZyYW1lKHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaW1tdW5lIl1dJGltbXVub2dyYW0uZGYpLCBkYXRhLnJvdyA9IDE5NCwgbWFpbiA9ICIiLCBhZGQgPSBUUlVFLCBjb2wgPSAicmVkIiwgbHR5ID0gNSkKI2xlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQ9YygiUGF0aWVudCIsICJULWNlbGzigJNyaWNoIiwiVC1jZWxs4oCTcG9vciIsICJULWNlbGzigJNpbnRlcm1lZGlhdGUiKSwgZmlsbD1jKCJibGFjayIsICJwb3dkZXJibHVlIiwgImZvcmVzdGdyZWVuIiwgInJlZCIpLCBidHk9Im4iLCBiZyA9ICJ0cmFuc3BhcmVudCIsIGNleCA9IDAuOCkKCiMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQojaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKYHIgaWYgKCBwYXJhbXMkaW1tdW5vZ3JhbSApIHsgYygiKioqIikgfWAKCmByIGlmICggcGFyYW1zJGltbXVub2dyYW0gKSB7IGMoIiMjIyMgVGFibGUgey50YWJzZXR9IikgfWAKCmBgYHtyIGltbXVub2dyYW1fdGFibGVfbGVnZW5kLCBlY2hvPUZBTFNFLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBwYXJhbXMkaW1tdW5vZ3JhbSwgcmVzdWx0cz0iYXNpcyJ9CmNhdCgiXG48ZGV0YWlscz5cbiIpCmNhdCgiXG48c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+XG4iKQpjYXQoIlxuPGZvbnQgc2l6ZT1cIjJcIj5cbiIpCmNhdCgiXG5UaGUgPHNwYW4gc3R5bGU9XCJjb2xvcjojZmYwMDAwXCI+UkVEPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmhpZ2ggZXhwcmVzc2lvbioqIChaLXNjb3JlKSB2YWx1ZXMgYW5kIDxzcGFuIHN0eWxlPVwiY29sb3I6IzAwMDBmZlwiPkJMVUU8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqbG93IGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGluIGluZGl2aWR1YWwgc2FtcGxlIGdyb3VwLiBUaGUgPHNwYW4gc3R5bGU9XCJjb2xvcjojODA4MDgwXCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW5kaWNhdGUgZ2VuZXMgd2l0aCAqKm5vL2xvdyBleHByZXNzaW9uKiouIFRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW4gaWxsdXN0cmF0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBaLXNjb3JlcyBpbiBwYXRpZW50IHNhbXBsZSBhbmQgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQgZm9yIGVhY2ggaW1tdW5vZ3JhbSBnZW5lLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKmRlY3JlYXNpbmcqKiBhYnNvbHV0ZSB2YWx1ZXMgaW4gdGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbi5cbiIpCmNhdCgiXG48L2ZvbnQ+XG4iKQpjYXQoIlxuPC9kZXRhaWxzPlxuIikKY2F0KCJcbioqKlxuIikKYGBgCgpgciBpZiAoIHBhcmFtcyRpbW11bm9ncmFtICkgeyBjKCIjIyMjIyBQZXJjZW50aWxlcyIpIH1gCgpgYGB7ciBpbW11bm9ncmFtX3RhYmxlX3BlcmMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHBhcmFtcyRpbW11bm9ncmFtfQp0YXJnZXRzIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbInNhbXBsZV9hbm5vdCJdXQpkYXRhIDwtIHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dCmdlbmVzIDwtIHVuaXF1ZSh1bmxpc3QocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19pbW11bmUiXV0kaW1tdW5vZ3JhbSRTWU1CT0wpKQoKIyMjIyMgRGVhbCB3aXRoIG5vIGdlbmVzIG9yIHdoZW4gbW9yZSB0aGFuIDEwIGdlbmVzIGFyZSBvZiBpbnRlcmVzdAppZiAoIGxlbmd0aChnZW5lcykgPT0gMCApIHsKICBnZW5lcyA8LSBOVUxMCn0KCiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgY2FuY2VyIGdlbmVzIGZyb20gT25jb0tCIGFuZCBVTUNDciAoaHR0cHM6Ly9naXRodWIuY29tL3ZsYWRzYXZlbGlldi9OR1NfVXRpbHMvYmxvYi9tYXN0ZXIvbmdzX3V0aWxzL3JlZmVyZW5jZV9kYXRhL2tleV9nZW5lcy91bWNjcl9jYW5jZXJfZ2VuZXMuMjAxOS0wMy0yMC50c3YpCmltbXVub2dyYW0uZXhwci5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIsICJDSUMiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInBlcmMiLCBzY2FsaW5nID0gc2NhbGluZylbWzFdXQoKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlCmltbXVub2dyYW0uZXhwci5wZXJjCgojIyMjIyBTYXZlIHRoZSBleHByZXNzaW9uIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1pbW11bm9ncmFtLmV4cHIucGVyYywgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJpbW11bm9ncmFtLmV4cHIucGVyYy5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CgojIyMjIyBDbGVhbiB0aGUgc3BhY2UKcm0oaW1tdW5vZ3JhbS5leHByLnBlcmMpCmBgYAoKYGBge3IgaW1tdW5vZ3JhbV90YWJsZV9sZWdlbmRfcGVyYywgZWNobz1GQUxTRSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcGFyYW1zJGltbXVub2dyYW0sIHJlc3VsdHM9ImFzaXMifQpjYXQoIlxuPGRldGFpbHM+XG4iKQpjYXQoIlxuPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5PlxuIikKY2F0KCJcbjxmb250IHNpemU9XCIyXCI+XG4iKQpjYXQoIlxuVGhlIDxzcGFuIHN0eWxlPVwiY29sb3I6I2ZmMDAwMFwiPlJFRDwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipoaWdoIGV4cHJlc3Npb24qKiAocGVyY2VudGlsZSkgdmFsdWVzIGFuZCA8c3BhbiBzdHlsZT1cImNvbG9yOiMwMDAwZmZcIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlIDxzcGFuIHN0eWxlPVwiY29sb3I6IzgwODA4MFwiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluZGljYXRlIGdlbmVzIHdpdGggKipuby9sb3cgZXhwcmVzc2lvbioqLiBUaGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uIGlsbHVzdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gcGVyY2VudGlsZXMgaW4gcGF0aWVudCBzYW1wbGUgYW5kIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0IGZvciBlYWNoIGltbXVub2dyYW0gZ2VuZS4gR2VuZXMgYXJlIG9yZGVyZWQgYnkgKipkZWNyZWFzaW5nKiogYWJzb2x1dGUgdmFsdWVzIGluIHRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW4uXG4iKQpjYXQoIlxuPC9mb250PlxuIikKY2F0KCJcbjwvZGV0YWlscz5cbiIpCmNhdCgiXG4qKipcbiIpCmBgYAoKYHIgaWYgKCBwYXJhbXMkaW1tdW5vZ3JhbSApIHsgYygiIyMjIyMgWi1zY29yZXMiKSB9YAoKYGBge3IgaW1tdW5vZ3JhbV90YWJsZSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsID0gcGFyYW1zJGltbXVub2dyYW19CiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgY2FuY2VyIGdlbmVzIGZyb20gT25jb0tCIGFuZCBVTUNDUiAoaHR0cHM6Ly9naXRodWIuY29tL3ZsYWRzYXZlbGlldi9OR1NfVXRpbHMvYmxvYi9tYXN0ZXIvbmdzX3V0aWxzL3JlZmVyZW5jZV9kYXRhL2tleV9nZW5lcy91bWNjcl9jYW5jZXJfZ2VuZXMuMjAxOS0wMy0yMC50c3YpCmltbXVub2dyYW0uZXhwci56IDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIsICJDSUMiKV0sIG9uY29rYl9hbm5vdCA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfb25jb2tiIl1dLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInoiLCBzY2FsaW5nID0gc2NhbGluZylbWzFdXQoKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlCmltbXVub2dyYW0uZXhwci56CgojIyMjIyBTYXZlIHRoZSBleHByZXNzaW9uIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1pbW11bm9ncmFtLmV4cHIueiwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJpbW11bm9ncmFtLmV4cHIuei5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CmBgYAoKIyMgSFJEIGdlbmVzCgpTZWN0aW9uIHByZXNlbnRpbmcgZXhwcmVzc2lvbiBsZXZlbHMgb2YgaG9tb2xvZ291cyByZWNvbWJpbmF0aW9uIGRlZmljaWVuY3kgKEhSRCkgZ2VuZXMgdG8gYXNzZXNzIGhvdyBtYW55IG9mIHRoZXNlIGRlbW9uc3RyYXRlIGxvdyBleHByZXNzaW9uLCB3aGljaCBtYXkgaW5kaWNhdGUgcG90ZW50aWFsIHByb21vdGVyIG1ldGh5bGF0aW9uIGV2ZW50cy4gVGhlaXIgbVJOQSBleHByZXNzaW9uIGxldmVscyBhcmUgcHJlc2VudGVkIGluIHBhdGllbnQncyBzYW1wbGUgYWxvbmcgdGhlaXIgYXZlcmFnZSBtUk5BIGV4cHJlc3Npb24gaW4gc2FtcGxlcyBmcm9tIGNhbmNlciBjb2hvcnRzLgoKT3V0IG9mIHRoZSBgciBsZW5ndGgodW5pcXVlKHVubGlzdChyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2hyZCJdXSRTWU1CT0wpKSlgIGhyZCBnZW5lcyB0aGUgZXhwcmVzc2lvbiBvZiAqKmByIGxlbmd0aCh3aGljaChyZWZfZ2VuZXMubGlzdFtbInN1bW1hcnkiXV0kSFJEICVpbiUgcm93bmFtZXMocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pKSlgKiogd2FzIHJlbGlhYmx5IG1lYXN1cmVkIGluIHBhdGllbnQncyBzYW1wbGUuIFRoZSByZW1haW5pbmcgYHIgbGVuZ3RoKHdoaWNoKHJlZl9nZW5lcy5saXN0W1sic3VtbWFyeSJdXSRIUkQgJSFpbiUgcm93bmFtZXMocmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0pKSlgIGdlbmVzIGFyZSBlaXRoZXIgbm90IGV4cHJlc3NlZCBvciB0aGVpciBleHByZXNzaW9uIGxldmVsIGlzIHRvbyBsb3cgdG8gYmUgZGV0ZWN0ZWQgKGluZGljYXRlZCBpbiA8c3BhbiBzdHlsZT0iY29sb3I6IzgwODA4MCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMpLgoKIyMjIC0gU3VtbWFyeSB0YWJsZSB7LnRhYnNldH0KCiMjIyMgUGVyY2VudGlsZXMKCmBgYHtyIGhyZF9nZW5lc190YWJsZV9wZXJjLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIFVwZGF0ZSBNeVNRTCBjb21tZW5kIHRvIHBvcHVsYXRlIFJOQS1zZXEgZGF0YSBwb3J0YWwKbXlzcWxfcG9wdWxhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlLCAiLEhSRCBnZW5lcyIpCm15c3FsX3BvcHVsYXRlX3VwZGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGVfdXBkYXRlLCAiLEhSRCBnZW5lcyIpCgojIyMjIyBHZW5lcmF0ZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUgZm9yIGhyZCBnZW5lcyBmcm9tIFJpY2hxcmQKdGFyZ2V0cyA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJzYW1wbGVfYW5ub3QiXV0KZGF0YSA8LSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXQpnZW5lcyA8LSB1bmlxdWUodW5saXN0KHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfaHJkIl1dJFNZTUJPTCkpCgojIyMjIyBEZWFsIHdpdGggbm8gZ2VuZXMKaWYgKCBsZW5ndGgoZ2VuZXMpID09IDAgKSB7CiAgZ2VuZXMgPC0gTlVMTAp9CgpocmRfZ2VuZXMuZXhwci5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAicGVyYyIsIHNjYWxpbmcgPSBzY2FsaW5nKQoKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlCmhyZF9nZW5lcy5leHByLnBlcmNbWzFdXQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9aHJkX2dlbmVzLmV4cHIucGVyY1tbMV1dLCBmaWxlPXBhc3RlKGV4cHJUYWJsZURpciwgImhyZF9nZW5lcy5leHByLnBlcmMuaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKfQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojZmYwMDAwIj5SRUQ8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqaGlnaCBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiMwMDAwZmYiPkJMVUU8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqbG93IGV4cHJlc3Npb24qKiAocGVyY2VudGlsZSkgdmFsdWVzIGluIGluZGl2aWR1YWwgc2FtcGxlIGdyb3VwLiBUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluZGljYXRlIGdlbmVzIHdpdGggKipuby9sb3cgZXhwcmVzc2lvbioqLiBUaGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uIGlsbHVzdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gcGVyY2VudGlsZXMgaW4gcGF0aWVudCBzYW1wbGUgYW5kIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0IGZvciBlYWNoIEhSRCBnZW5lLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKmRlY3JlYXNpbmcqKiBhYnNvbHV0ZSB2YWx1ZXMgaW4gdGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbi4gKlRTRyogLSB0dW1vdXIgc3VwcHJlc3NvciBnZW5lCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCioqKgoKIyMjIyBaLXNjb3JlcwoKYGBge3IgaHJkX2dlbmVzX3RhYmxlLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgaHJkIGdlbmVzIGZyb20gUmljaGFyZApocmRfZ2VuZXMuZXhwci56IDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAieiIsIHNjYWxpbmcgPSBzY2FsaW5nKQoKIyMjIyMgUHJlc2VudCB0aGUgZXhwcmVzc2lvbiBzdW1tYXJ5IHRhYmxlCmhyZF9nZW5lcy5leHByLnpbWzFdXQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9aHJkX2dlbmVzLmV4cHIueltbMV1dLCBmaWxlPXBhc3RlKGV4cHJUYWJsZURpciwgImhyZF9nZW5lcy5leHByLnouaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKfQoKIyMjIyMgQ2xlYW4gdGhlIHNwYWNlCnJtKGhyZF9nZW5lcy5leHByLnopCmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiNmZjAwMDAiPlJFRDwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipoaWdoIGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGFuZCA8c3BhbiBzdHlsZT0iY29sb3I6IzAwMDBmZiI+QkxVRTwvc3Bhbj4gY29sb3VyIHJhbmdlIGluZGljYXRlIHJlbGF0aXZlbHkgKipsb3cgZXhwcmVzc2lvbioqIChaLXNjb3JlKSB2YWx1ZXMgaW4gaW5kaXZpZHVhbCBzYW1wbGUgZ3JvdXAuIFRoZSA8c3BhbiBzdHlsZT0iY29sb3I6IzgwODA4MCI+QkxBTks8L3NwYW4+IGNlbGxzIHdpdGggbWlzc2luZyB2YWx1ZXMgaW5kaWNhdGUgZ2VuZXMgd2l0aCAqKm5vL2xvdyBleHByZXNzaW9uKiouIFRoZSAqKkRpZmYqKiAoKipQYXRpZW50IHZzIGByIGNvbXBfY2FuY2VyX2dyb3VwYCoqKSBjb2x1bW4gaWxsdXN0cmF0ZXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBaLXNjb3JlcyBpbiBwYXRpZW50IHNhbXBsZSBhbmQgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQgZm9yIGVhY2ggSFJEIGdlbmUuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uLgoKPC9mb250Pgo8L2RldGFpbHM+CgoqKioKCiMjIyAtIEV4cHJlc3Npb24gb3ZlcnZpZXcgey50YWJzZXR9CgpPdmVydmlldyBvZiBIUkQgZ2VuZXMgZXhwcmVzc2lvbiBwcm9maWxlcyBpbiBwYXRpZW50J3Mgc2FtcGxlIGFuZCBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIHBhdGllbnRzLgoKIyMjIyBQZXJjZW50aWxlcwoKYGBge3IgZ2xhbmNlX2V4cHJfcGxvdF9ocmRfZ2VuZXNfcGVyYywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gM30Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCgojIyMjIyBHZW5lcmF0ZSBvdmVydmlldyBib3hwbG90CmlmICggIWlzLm51bGwoZ2VuZXMpICkgewogIGdsYW5jZUV4cHJQbG90KGdlbmVzID0gZ2VuZXMsIGRhdGEgPSBkYXRhLCB0YXJnZXRzID0gdGFyZ2V0cywgc2FtcGxlTmFtZSA9IHNhbXBsZV9uYW1lLCBleHRfY2FuY2VyID0gZXh0X2NhbmNlcl9ncm91cCwgaW50X2NhbmNlciA9IGludF9jYW5jZXJfZ3JvdXAsIGNvbXBfY2FuY2VyID0gY29tcF9jYW5jZXJfZ3JvdXAsIGFkZF9jYW5jZXIgPSBhZGRfY2FuY2VyX2dyb3VwLCBoZXhjb2RlID0gImhyZF9nZW5lcyIsIHR5cGUgPSAicGVyYyIsIHNvcnQgPSAiYWxwaGFiZXRpY2FsbHkiLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQp9IGVsc2UgewogIGNhdCgiXG5ObyBleHByZXNzaW9uIGRhdGEgaXMgYXZhaWxhYmxlIGZvciBIUkQgZ2VuZXMhXG4iKQp9CgojIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5QbG90IGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpUaGUgaW5kaXZpZHVhbCBib3goZXMpIHJlcHJlc2VudCB0aGUgYHIgaWYgKCBpbnRfY2FuY2VyX2dyb3VwID09IGNvbXBfY2FuY2VyX2dyb3VwICkgeyBwYXN0ZTAoaW50X2NhbmNlcl9ncm91cCwgIiBhbmQiKSB9IGVsc2UgeyBjYXQoIiIpIH1gIGByIGV4dF9jYW5jZXJfZ3JvdXBgIGByIGlmICggIWlzLm51bGwoYWRkX2NhbmNlcl9ncm91cCkgKSB7IHBhc3RlMCgiYW5kICIsIGFkZF9jYW5jZXJfZ3JvdXApIH0gZWxzZSB7IGNhdCgiIikgfWAgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQocyksIGFuZCB0aGUgKipCTEFDSyoqIGRvdHMgaW5kaWNhdGUgZXhwcmVzc2lvbiAocGVyY2VudGlsZSkgdmFsdWVzIGZvciBlYWNoIGdlbmUgaW4gdGhlIHBhdGllbnQgc2FtcGxlLiBHZW5lcyBhcmUgb3JkZXJlZCAgKiphbHBoYWJldGljYWxseSoqLgoKPC9mb250Pgo8L2RldGFpbHM+CgoqKioKCiMjIyMgWi1zY29yZXMKCmBgYHtyIGdsYW5jZV9leHByX3Bsb3RfaHJkX2dlbmVzLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSAzfQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGxvdGx5KSkKCiMjIyMjIEdlbmVyYXRlIG92ZXJ2aWV3IGJveHBsb3QKaWYgKCAhaXMubnVsbChnZW5lcykgKSB7CiAgZ2xhbmNlRXhwclBsb3QoZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGhleGNvZGUgPSAiaHJkX2dlbmVzIiwgdHlwZSA9ICJ6Iiwgc29ydCA9ICJhbHBoYWJldGljYWxseSIsIHNjYWxpbmcgPSBzY2FsaW5nLCByZXBvcnRfZGlyID0gcmVzdWx0c19kaXIpCn0gZWxzZSB7CiAgY2F0KCJcbk5vIGV4cHJlc3Npb24gZGF0YSBpcyBhdmFpbGFibGUgZm9yIEhSRCBnZW5lcyFcbiIpCn0KCiMjIyMjIERldGFjaCBwbG90bHkgcGFja2FnZS4gT3RoZXJ3aXNlIGl0IGNsYXNoZXMgd2l0aCBvdGhlciBncmFwaGljcyBkZXZpY2VzCmRldGFjaCgicGFja2FnZTpwbG90bHkiLCB1bmxvYWQ9RkFMU0UpCgojIyMjIENsZWFyIHBsb3RzIHRvIGZyZWUgdXAgc29tZSBtZW1vcnkKaWYoIWlzLm51bGwoZGV2Lmxpc3QoKSkpIGludmlzaWJsZShkZXYub2ZmKCkpCmBgYAoKPGRldGFpbHM+CjxzdW1tYXJ5PlBsb3QgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSBpbmRpdmlkdWFsIGJveChlcykgcmVwcmVzZW50IHRoZSBgciBpZiAoIGludF9jYW5jZXJfZ3JvdXAgPT0gY29tcF9jYW5jZXJfZ3JvdXAgKSB7IHBhc3RlMChpbnRfY2FuY2VyX2dyb3VwLCAiIGFuZCIpIH0gZWxzZSB7IGNhdCgiIikgfWAgYHIgZXh0X2NhbmNlcl9ncm91cGAgYHIgaWYgKCAhaXMubnVsbChhZGRfY2FuY2VyX2dyb3VwKSApIHsgcGFzdGUwKCJhbmQgIiwgYWRkX2NhbmNlcl9ncm91cCkgfSBlbHNlIHsgY2F0KCIiKSB9YCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydChzKSwgYW5kIHRoZSAqKkJMQUNLKiogZG90cyBpbmRpY2F0ZSBleHByZXNzaW9uIChaLXNjb3JlKSB2YWx1ZXMgZm9yIGVhY2ggZ2VuZSBpbiB0aGUgcGF0aWVudCBzYW1wbGUuIEdlbmVzIGFyZSBvcmRlcmVkICAqKmFscGhhYmV0aWNhbGx5KiouCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCioqKgoKIyMgQ2FuY2VyIGdlbmVzCgptUk5BIGV4cHJlc3Npb24gbGV2ZWxzIG9mIGNhbmNlciBnZW5lcyBpbiBwYXRpZW50J3Mgc2FtcGxlIGFuZCB0aGVpciBhdmVyYWdlIG1STkEgZXhwcmVzc2lvbiBpbiBzYW1wbGVzIGZyb20gY2FuY2VyIGNvaG9ydHMuIFRoZXNlIGluY2x1ZGUgZ2VuZXMgcmVwb3J0ZWQgaW4gdGhlIGZvbGxvd2luZyBnZW5lIHBhbmVscy9yZXNvdXJjZXMgKltVTUNDUiBjYW5jZXIgZ2VuZXNdKGh0dHBzOi8vZ2l0aHViLmNvbS92bGFkc2F2ZWxpZXYvTkdTX1V0aWxzL2Jsb2IvbWFzdGVyL25nc191dGlscy9yZWZlcmVuY2VfZGF0YS9rZXlfZ2VuZXMvdW1jY3JfY2FuY2VyX2dlbmVzLjIwMTktMDMtMjAudHN2KXt0YXJnZXQ9Il9ibGFuayJ9KiwgKltPbmNvS0JdKGh0dHA6Ly9vbmNva2Iub3JnLyMvY2FuY2VyR2VuZXMpe3RhcmdldD0iX2JsYW5rIn0qLCAqW01TSy1JTVBBQ1RdKGh0dHBzOi8vd3d3Lm1za2NjLm9yZy9tc2staW1wYWN0KXt0YXJnZXQ9Il9ibGFuayJ9KiwgKltNU0stSEVNRV0oaHR0cDovL3d3dy5pc2xoLm9yZy9QcmVzZW50YXRpb25fVXBsb2FkL3ByZXNlbnRhdGlvbl91cGxvYWRzLzEyXzUyXzA5MDAtWmVoaXIucGRmKXt0YXJnZXQ9Il9ibGFuayJ9KiwgKltGb3VuZGF0aW9uIE9uZV0oaHR0cHM6Ly93d3cuZm91bmRhdGlvbm1lZGljaW5lLmNvbS9nZW5vbWljLXRlc3RpbmcvZm91bmRhdGlvbi1vbmUtY2R4KXt0YXJnZXQ9Il9ibGFuayJ9KiwgKltGb3VuZGF0aW9uIE9uZSBIZW1lXShodHRwczovL3d3dy5mb3VuZGF0aW9ubWVkaWNpbmUuY29tL2dlbm9taWMtdGVzdGluZy9mb3VuZGF0aW9uLW9uZS1oZW1lKXt0YXJnZXQ9Il9ibGFuayJ9KiwgKltWb2dlbHN0ZWluXShodHRwOi8vc2NpZW5jZS5zY2llbmNlbWFnLm9yZy9jb250ZW50LzMzOS82MTI3LzE1NDYuZnVsbCl7dGFyZ2V0PSJfYmxhbmsifSogYW5kICpbU2FuZ2VyIENhbmNlciBHZW5lIENlbnN1c10oaHR0cHM6Ly93d3cuc2FuZ2VyLmFjLnVrL3NjaWVuY2UvZGF0YS9jYW5jZXItZ2VuZS1jZW5zdXMpe3RhcmdldD0iX2JsYW5rIn0qIChDR0MpLgoKIyMjIC0gU3VtbWFyeSB0YWJsZSB7LnRhYnNldH0KCk91dCBvZiB0aGUgYHIgbnJvdyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSlgIGNhbmNlciBnZW5lcyB0aGUgZXhwcmVzc2lvbiBvZiAqKmByIGxlbmd0aCh3aGljaChyb3duYW1lcyhyZWZfZ2VuZXMubGlzdFtbImdlbmVzX2NhbmNlciJdXSkgJWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKWAqKiB3YXMgcmVsaWFibHkgbWVhc3VyZWQgaW4gcGF0aWVudCdzIHNhbXBsZS4gVGhlIHJlbWFpbmluZyBgciBsZW5ndGgod2hpY2gocm93bmFtZXMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0pICUhaW4lIHJvd25hbWVzKHJlZl9kYXRhc2V0Lmxpc3RbW2RhdGFzZXRdXVtbImRhdGFfdG9fcmVwb3J0Il1dKSkpYCBnZW5lcyBhcmUgZWl0aGVyIG5vdCBleHByZXNzZWQgb3IgdGhlaXIgZXhwcmVzc2lvbiBsZXZlbCBpcyB0b28gbG93IHRvIGJlIGRldGVjdGVkIChpbmRpY2F0ZWQgaW4gPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzKS4KCiMjIyMgUGVyY2VudGlsZXMKCmBgYHtyIGNhbmNlcl9nZW5lc190YWJsZV9wZXJjLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIFVwZGF0ZSBNeVNRTCBjb21tZW5kIHRvIHBvcHVsYXRlIFJOQS1zZXEgZGF0YSBwb3J0YWwKbXlzcWxfcG9wdWxhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlLCAiLENhbmNlciBnZW5lcyxBbGwgZ2VuZXMiKQpteXNxbF9wb3B1bGF0ZV91cGRhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlX3VwZGF0ZSwgIixDYW5jZXIgZ2VuZXMsQWxsIGdlbmVzIikKCiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgY2FuY2VyIGdlbmVzIGZyb20gT25jb0tCIGFuZCBVTUNDUiAoaHR0cHM6Ly9naXRodWIuY29tL3ZsYWRzYXZlbGlldi9OR1NfVXRpbHMvYmxvYi9tYXN0ZXIvbmdzX3V0aWxzL3JlZmVyZW5jZV9kYXRhL2tleV9nZW5lcy91bWNjcl9jYW5jZXJfZ2VuZXMuMjAxOS0wMy0yMC50c3YpCnRhcmdldHMgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1sic2FtcGxlX2Fubm90Il1dCmRhdGEgPC0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZGF0YV90b19yZXBvcnQiXV0KZ2VuZXMgPC0gcm93bmFtZXMocmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0pCgojIyMjIyBEZWFsIHdpdGggbm8gZ2VuZXMKaWYgKCBsZW5ndGgoZ2VuZXMpID09IDAgKSB7CiAgZ2VuZXMgPC0gTlVMTAp9CgpjYW5jZXJfZ2VuZXMuZXhwci5wZXJjIDwtIGV4cHJUYWJsZSggZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGdlbmVzX2Fubm90ID0gcmVmX2RhdGFzZXQubGlzdFtbZGF0YXNldF1dW1siZ2VuZV9hbm5vdF9hbGwiXV1bLCBjKCJTWU1CT0wiLCAiRU5TRU1CTCIpXSwgb25jb2tiX2Fubm90ID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19vbmNva2IiXV0sIGNhbmNlcl9nZW5lcyA9IHJlZl9nZW5lcy5saXN0W1siZ2VuZXNfY2FuY2VyIl1dLCBleHRfbGlua3MgPSBUUlVFLCB0eXBlID0gInBlcmMiLCBzY2FsaW5nID0gc2NhbGluZykKCiMjIyMjIFByZXNlbnQgdGhlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZQpjYW5jZXJfZ2VuZXMuZXhwci5wZXJjW1sxXV0KCiMjIyMjIFNhdmUgdGhlIGV4cHJlc3Npb24gdGFibGUgYXMgaHRtbCBmaWxlCmlmICggcGFyYW1zJHNhdmVfdGFibGVzICkgewogIHNhdmVXaWRnZXRGaXgod2lkZ2V0PWNhbmNlcl9nZW5lcy5leHByLnBlcmNbWzFdXSwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJjYW5jZXJfZ2VuZXMuZXhwci5wZXJjLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KClRoZSA8c3BhbiBzdHlsZT0iY29sb3I6I2ZmMDAwMCI+UkVEPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmhpZ2ggZXhwcmVzc2lvbioqIChwZXJjZW50aWxlKSB2YWx1ZXMgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjojMDAwMGZmIj5CTFVFPC9zcGFuPiBjb2xvdXIgcmFuZ2UgaW5kaWNhdGUgcmVsYXRpdmVseSAqKmxvdyBleHByZXNzaW9uKiogKHBlcmNlbnRpbGUpIHZhbHVlcyBpbiBpbmRpdmlkdWFsIHNhbXBsZSBncm91cC4gVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojODA4MDgwIj5CTEFOSzwvc3Bhbj4gY2VsbHMgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbmRpY2F0ZSBnZW5lcyB3aXRoICoqbm8vbG93IGV4cHJlc3Npb24qKi4gVGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbiBpbGx1c3RyYXRlcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHBlcmNlbnRpbGVzIGluIHBhdGllbnQgc2FtcGxlIGFuZCByZWZlcmVuY2UgY2FuY2VyIGNvaG9ydCBmb3IgZWFjaCBjYW5jZXIgZ2VuZS4gR2VuZXMgY29uc2lkZXJlZCB0byBiZSBvbmNvZ2VuZXMgb3IgdHVtb3VyIHN1cHByZXNzb3IgZ2VuZXMsIGFjY29yZGluZyB0byBbT25jb0tCXShodHRwOi8vb25jb2tiLm9yZy8jL2NhbmNlckdlbmVzKXt0YXJnZXQ9Il9ibGFuayJ9IGRhdGFiYXNlLCBhbmQgaW5jbHVzaW9uIGluIHZhcmlvdXMgc2VxdWVuY2luZyBwYW5lbHMgYXJlIGFsc28gaW5kaWNhdGVkLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKmRlY3JlYXNpbmcqKiBhYnNvbHV0ZSB2YWx1ZXMgaW4gdGhlICoqRGlmZioqICgqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiopIGNvbHVtbi4gKlRTRyogLSB0dW1vdXIgc3VwcHJlc3NvciBnZW5lCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCioqKgoKIyMjIyBaLXNjb3JlcwoKYGBge3IgY2FuY2VyX2dlbmVzX3RhYmxlLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIEdlbmVyYXRlIGV4cHJlc3Npb24gc3VtbWFyeSB0YWJsZSBmb3IgY2FuY2VyIGdlbmVzIGZyb20gT25jb0tCIGFuZCBVTUNDUiAoaHR0cHM6Ly9naXRodWIuY29tL3ZsYWRzYXZlbGlldi9OR1NfVXRpbHMvYmxvYi9tYXN0ZXIvbmdzX3V0aWxzL3JlZmVyZW5jZV9kYXRhL2tleV9nZW5lcy91bWNjcl9jYW5jZXJfZ2VuZXMuMjAxOS0wMy0yMC50c3YpCmNhbmNlcl9nZW5lcy5leHByLnogPC0gZXhwclRhYmxlKCBnZW5lcyA9IGdlbmVzLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgZ2VuZXNfYW5ub3QgPSByZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJnZW5lX2Fubm90X2FsbCJdXVssIGMoIlNZTUJPTCIsICJFTlNFTUJMIildLCBvbmNva2JfYW5ub3QgPSByZWZfZ2VuZXMubGlzdFtbImdlbmVzX29uY29rYiJdXSwgY2FuY2VyX2dlbmVzID0gcmVmX2dlbmVzLmxpc3RbWyJnZW5lc19jYW5jZXIiXV0sIGV4dF9saW5rcyA9IFRSVUUsIHR5cGUgPSAieiIsIHNjYWxpbmcgPSBzY2FsaW5nKVtbMV1dCgojIyMjIyBQcmVzZW50IHRoZSBleHByZXNzaW9uIHN1bW1hcnkgdGFibGUKY2FuY2VyX2dlbmVzLmV4cHIuegoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9Y2FuY2VyX2dlbmVzLmV4cHIueiwgZmlsZT1wYXN0ZShleHByVGFibGVEaXIsICJjYW5jZXJfZ2VuZXMuZXhwci56Lmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KCiMjIyMjIENsZWFuIHRoZSBzcGFjZQpybShjYW5jZXJfZ2VuZXMuZXhwci56KQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIDxzcGFuIHN0eWxlPSJjb2xvcjojZmYwMDAwIj5SRUQ8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqaGlnaCBleHByZXNzaW9uKiogKFotc2NvcmUpIHZhbHVlcyBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiMwMDAwZmYiPkJMVUU8L3NwYW4+IGNvbG91ciByYW5nZSBpbmRpY2F0ZSByZWxhdGl2ZWx5ICoqbG93IGV4cHJlc3Npb24qKiAoWi1zY29yZSkgdmFsdWVzIGluIGluZGl2aWR1YWwgc2FtcGxlIGdyb3VwLiBUaGUgPHNwYW4gc3R5bGU9ImNvbG9yOiM4MDgwODAiPkJMQU5LPC9zcGFuPiBjZWxscyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluZGljYXRlIGdlbmVzIHdpdGggKipuby9sb3cgZXhwcmVzc2lvbioqLiBUaGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uIGlsbHVzdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gWi1zY29yZXMgaW4gcGF0aWVudCBzYW1wbGUgYW5kIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0IGZvciBlYWNoIGNhbmNlciBnZW5lLiBHZW5lcyBjb25zaWRlcmVkIHRvIGJlIG9uY29nZW5lcyBvciB0dW1vdXIgc3VwcHJlc3NvciBnZW5lcywgYWNjb3JkaW5nIHRvIFtPbmNvS0JdKGh0dHA6Ly9vbmNva2Iub3JnLyMvY2FuY2VyR2VuZXMpe3RhcmdldD0iX2JsYW5rIn0gZGF0YWJhc2UsIGFuZCBpbmNsdXNpb24gaW4gdmFyaW91cyBzZXF1ZW5jaW5nIHBhbmVscyBhcmUgYWxzbyBpbmRpY2F0ZWQuIEdlbmVzIGFyZSBvcmRlcmVkIGJ5ICoqZGVjcmVhc2luZyoqIGFic29sdXRlIHZhbHVlcyBpbiB0aGUgKipEaWZmKiogKCoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKikgY29sdW1uLiAqVFNHKiAtIHR1bW91ciBzdXBwcmVzc29yIGdlbmUKCjwvZm9udD4KPC9kZXRhaWxzPgoKKioqCgojIyMgLSBFeHByZXNzaW9uIG92ZXJ2aWV3IHsudGFic2V0fQoKT3ZlcnZpZXcgb2YgZXhwcmVzc2lvbiBwcm9maWxlcyBvZiA1MCBhbHRlcmVkIGNhbmNlciBnZW5lcyB3aXRoIHRoZSBncmVhdGVzdCBkaWZmZXJlbmNlIGluIG1STkEgZXhwcmVzc2lvbiAocGVyY2VudGlsZSkgdmFsdWVzIGJldHdlZW4gcGF0aWVudCdzIHNhbXBsZSBhbmQgdGhlIGF2ZXJhZ2UgbVJOQSBleHByZXNzaW9uIGluIHNhbXBsZXMgZnJvbSBjYW5jZXIgcGF0aWVudHMuCgojIyMjIFBlcmNlbnRpbGVzCgpgYGB7ciBnbGFuY2VfZXhwcl9wbG90X2NhbmNlcl9nZW5lc19wZXJjLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSAzfQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkocGxvdGx5KSkKCiMjIyMjIEdlbmVyYXRlIG92ZXJ2aWV3IGJveHBsb3QKZ2VuZXMgPC0gY2FuY2VyX2dlbmVzLmV4cHIucGVyY1tbMl1dJFNZTUJPTFsxOjUwXQoKaWYgKCAhaXMubnVsbChnZW5lcykgKSB7CiAgZ2xhbmNlRXhwclBsb3QoZ2VuZXMgPSBnZW5lcywgZGF0YSA9IGRhdGEsIHRhcmdldHMgPSB0YXJnZXRzLCBzYW1wbGVOYW1lID0gc2FtcGxlX25hbWUsIGV4dF9jYW5jZXIgPSBleHRfY2FuY2VyX2dyb3VwLCBpbnRfY2FuY2VyID0gaW50X2NhbmNlcl9ncm91cCwgY29tcF9jYW5jZXIgPSBjb21wX2NhbmNlcl9ncm91cCwgYWRkX2NhbmNlciA9IGFkZF9jYW5jZXJfZ3JvdXAsIGhleGNvZGUgPSAiY2FuY2VyX2dlbmVzIiwgdHlwZSA9ICJwZXJjIiwgc29ydCA9ICJub25lIiwgc2NhbGluZyA9IHNjYWxpbmcsIHJlcG9ydF9kaXIgPSByZXN1bHRzX2RpcikKfSBlbHNlIHsKICBjYXQoIlxuTm8gZXhwcmVzc2lvbiBkYXRhIGlzIGF2YWlsYWJsZSBmb3IgY2FuY2VyIGdlbmVzIVxuIikKfQoKIyMjIyMgRGV0YWNoIHBsb3RseSBwYWNrYWdlLiBPdGhlcndpc2UgaXQgY2xhc2hlcyB3aXRoIG90aGVyIGdyYXBoaWNzIGRldmljZXMKZGV0YWNoKCJwYWNrYWdlOnBsb3RseSIsIHVubG9hZD1GQUxTRSkKCiMjIyMgQ2xlYXIgcGxvdHMgdG8gZnJlZSB1cCBzb21lIG1lbW9yeQppZighaXMubnVsbChkZXYubGlzdCgpKSkgaW52aXNpYmxlKGRldi5vZmYoKSkKYGBgCgo8ZGV0YWlscz4KPHN1bW1hcnk+UGxvdCBsZWdlbmQ8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKVGhlIGluZGl2aWR1YWwgYm94KGVzKSByZXByZXNlbnQgdGhlIGByIGlmICggaW50X2NhbmNlcl9ncm91cCA9PSBjb21wX2NhbmNlcl9ncm91cCApIHsgcGFzdGUwKGludF9jYW5jZXJfZ3JvdXAsICIgYW5kIikgfSBlbHNlIHsgY2F0KCIiKSB9YCBgciBleHRfY2FuY2VyX2dyb3VwYCBgciBpZiAoICFpcy5udWxsKGFkZF9jYW5jZXJfZ3JvdXApICkgeyBwYXN0ZTAoImFuZCAiLCBhZGRfY2FuY2VyX2dyb3VwKSB9IGVsc2UgeyBjYXQoIiIpIH1gIHJlZmVyZW5jZSBjYW5jZXIgY29ob3J0KHMpLCBhbmQgdGhlICoqQkxBQ0sqKiBkb3RzIGluZGljYXRlIGV4cHJlc3Npb24gKHBlcmNlbnRpbGUpIHZhbHVlcyBmb3IgZWFjaCBnZW5lIGluIHRoZSBwYXRpZW50IHNhbXBsZS4gR2VuZXMgYXJlIG9yZGVyZWQgYnkgKipkZWNyZWFzaW5nKiogYWJzb2x1dGUgdmFsdWVzIGluIHRoZSAqKlBhdGllbnQgdnMgYHIgY29tcF9jYW5jZXJfZ3JvdXBgKiogY29tcGFyaXNvbi4KCjwvZm9udD4KPC9kZXRhaWxzPgoKKioqCgojIyMjIFotc2NvcmVzCgpgYGB7ciBnbGFuY2VfZXhwcl9wbG90X2NhbmNlcl9nZW5lcywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gM30Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHBsb3RseSkpCgppZiAoICFpcy5udWxsKGdlbmVzKSApIHsKICBnbGFuY2VFeHByUGxvdChnZW5lcyA9IGdlbmVzLCBkYXRhID0gZGF0YSwgdGFyZ2V0cyA9IHRhcmdldHMsIHNhbXBsZU5hbWUgPSBzYW1wbGVfbmFtZSwgZXh0X2NhbmNlciA9IGV4dF9jYW5jZXJfZ3JvdXAsIGludF9jYW5jZXIgPSBpbnRfY2FuY2VyX2dyb3VwLCBjb21wX2NhbmNlciA9IGNvbXBfY2FuY2VyX2dyb3VwLCBhZGRfY2FuY2VyID0gYWRkX2NhbmNlcl9ncm91cCwgaGV4Y29kZSA9ICJjYW5jZXJfZ2VuZXMiLCB0eXBlID0gInoiLCBzb3J0ID0gIm5vbmUiLCBzY2FsaW5nID0gc2NhbGluZywgcmVwb3J0X2RpciA9IHJlc3VsdHNfZGlyKQp9IGVsc2UgewogIGNhdCgiXG5ObyBleHByZXNzaW9uIGRhdGEgaXMgYXZhaWxhYmxlIGZvciBjYW5jZXIgZ2VuZXMhXG4iKQp9CgojIyMjIyBEZXRhY2ggcGxvdGx5IHBhY2thZ2UuIE90aGVyd2lzZSBpdCBjbGFzaGVzIHdpdGggb3RoZXIgZ3JhcGhpY3MgZGV2aWNlcwpkZXRhY2goInBhY2thZ2U6cGxvdGx5IiwgdW5sb2FkPUZBTFNFKQoKIyMjIyBDbGVhciBwbG90cyB0byBmcmVlIHVwIHNvbWUgbWVtb3J5CmlmKCFpcy5udWxsKGRldi5saXN0KCkpKSBpbnZpc2libGUoZGV2Lm9mZigpKQpgYGAKCjxkZXRhaWxzPgo8c3VtbWFyeT5QbG90IGxlZ2VuZDwvc3VtbWFyeT4KPGZvbnQgc2l6ZT0iMiI+CgpUaGUgaW5kaXZpZHVhbCBib3goZXMpIHJlcHJlc2VudCB0aGUgYHIgaWYgKCBpbnRfY2FuY2VyX2dyb3VwID09IGNvbXBfY2FuY2VyX2dyb3VwICkgeyBwYXN0ZTAoaW50X2NhbmNlcl9ncm91cCwgIiBhbmQiKSB9IGVsc2UgeyBjYXQoIiIpIH1gIGByIGV4dF9jYW5jZXJfZ3JvdXBgIGByIGlmICggIWlzLm51bGwoYWRkX2NhbmNlcl9ncm91cCkgKSB7IHBhc3RlMCgiYW5kICIsIGFkZF9jYW5jZXJfZ3JvdXApIH0gZWxzZSB7IGNhdCgiIikgfWAgcmVmZXJlbmNlIGNhbmNlciBjb2hvcnQocyksIGFuZCB0aGUgKipCTEFDSyoqIGRvdHMgaW5kaWNhdGUgZXhwcmVzc2lvbiAoWi1zY29yZSkgdmFsdWVzIGZvciBlYWNoIGdlbmUgaW4gdGhlIHBhdGllbnQgc2FtcGxlLiBHZW5lcyBhcmUgb3JkZXJlZCBieSAqKmRlY3JlYXNpbmcqKiBhYnNvbHV0ZSB2YWx1ZXMgaW4gdGhlICoqUGF0aWVudCB2cyBgciBjb21wX2NhbmNlcl9ncm91cGAqKiBjb21wYXJpc29uLgoKPC9mb250Pgo8L2RldGFpbHM+CgoqKioKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCIjIyBEcnVnIG1hdGNoaW5nIHsudGFic2V0fSIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiTGlzdCBvZiBkcnVncyB0YXJnZXRpbmcgdmFyaWFudHMgaW4gZGV0ZWN0ZWQgW011dGF0ZWQgZ2VuZXNdLCBbRnVzaW9uIGdlbmVzXSwgW1N0cnVjdHVyYWwgdmFyaWFudHNdLWFmZmVjdGVkIGdlbmVzLCBbQ04gYWx0ZXJlZCBnZW5lc10sIFtIUkQgZ2VuZXNdIGFuZCBkeXNyZWd1bGF0ZWQgW0NhbmNlciBnZW5lc10sIHdoaWNoIGNhbiBiZSBjb25zaWRlcmVkIGluIHRoZSB0cmVhdG1lbnQgZGVjaXNpb24gbWFraW5nIHByb2Nlc3MuIFRoZSBjbGluaWNhbGx5IGFjdGlvbmFibGUgYWJlcnJhdGlvbnMgYXJlIG1hdGNoZWQgYmFzZWQgb24gaW5mb3JtYXRpb24gcHJvdmlkZWQgYnkgKltjbGluaWNhbCBpbnRlcnByZXRhdGlvbnMgb2YgdmFyaWFudHMgaW4gQ2FuY2VyXShodHRwczovL2NpdmljZGIub3JnL2hvbWUpe3RhcmdldD1cIl9ibGFua1wifSogKENJVmlDKSAoW0dyaWZmaXRoIGV0IGFsLiAoMjAxNyldKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzI4MTM4MTUzKXt0YXJnZXQ9XCJfYmxhbmtcIn0pLiBUaGUgZXZpZGVuY2UgcGVydGFpbmluZyB0byB2YXJpYW50cyBlZmZlY3Qgb24gdGhlcmFwZXV0aWMgcmVzcG9uc2UgaXMgYWxzbyBwcm92aWRlZC4iKSB9YAoKYGBge3IgZHJ1Z3NfdGFibGVfZGlyLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIENyZWF0ZSBkaXJlY3RvcnkgZm9yIHRhYmxlcwpkcnVnc1RhYmxlRGlyIDwtIHBhc3RlKHJlc3VsdHNfZGlyLCAiZHJ1Z3NUYWJsZXMiLCBzZXAgPSAiLyIpCmlmICggIWZpbGUuZXhpc3RzKGRydWdzVGFibGVEaXIpICkgewogIGRpci5jcmVhdGUoZHJ1Z3NUYWJsZURpciwgcmVjdXJzaXZlPVRSVUUpCn0KYGBgCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiIyMjIC0gTXV0YXRlZCBnZW5lcyAtIikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICYmICFydW5QY2dyQ2h1bmsgKSB7IGMoIk11dGF0aW9uIGRhdGEgZm9yIHRoaXMgc2FtcGxlIGlzICoqTk9UIEFWQUlMQUJMRSoqLiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyAmJiBydW5QY2dyQ2h1bmsgKSB7IHBhc3RlMCgiKioiLCBsZW5ndGgobXV0X2dlbmVzLmV4cHIucGVyY1tbMl1dJFNZTUJPTCksICIqKiBnZW5lcyB3aXRoIFtQQ0dSXShodHRwczovL2dpdGh1Yi5jb20vc2lndmVuL3BjZ3Ipe3RhcmdldD1cIl9ibGFua1wifSBbdGllcl0oaHR0cHM6Ly9wY2dyLnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC90aWVyX3N5c3RlbXMuaHRtbCN0aWVyLW1vZGVsLTItcGNnci1hY21nKXt0YXJnZXQ9XCJfYmxhbmtcIn0gMS0iLCBwYXJhbXMkcGNncl90aWVyLCAiIHZhcmlhbnRzIHdlcmUgc2NyZWVuZWQgZm9yIHN1aXRhYmxlIGRydWdzIChzZWUgW011dGF0ZWQgZ2VuZXNdIHNlY3Rpb24pLiIpIH0gZWxzZSBpZiAoIHBhcmFtcyRkcnVncyAmJiAhcnVuUGNnckNodW5rICkgeyBwYXN0ZTAoIioqMCoqIGdlbmVzIHdpdGggW1BDR1JdKGh0dHBzOi8vZ2l0aHViLmNvbS9zaWd2ZW4vcGNncil7dGFyZ2V0PVwiX2JsYW5rXCJ9IFt0aWVyXShodHRwczovL3BjZ3IucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L3RpZXJfc3lzdGVtcy5odG1sI3RpZXItbW9kZWwtMi1wY2dyLWFjbWcpe3RhcmdldD1cIl9ibGFua1wifSAxLSIsIHBhcmFtcyRwY2dyX3RpZXIsICIgdmFyaWFudHMgd2VyZSBzY3JlZW5lZCBmb3Igc3VpdGFibGUgZHJ1Z3MgKHNlZSBbTXV0YXRlZCBnZW5lc10gc2VjdGlvbikuIikgfWAKCmBgYHtyIGRydWdzX3ByZWRpY3RpdmVfbXV0X2dlbmVzLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBydW5QY2dyQ2h1bmt9CiMjIyMjIEdlbmVyYXRlIHRhYmxlIHdpdGggZHJ1Z3MgdGFyZ2V0aW5nIG11dGF0ZWQgY2FuY2VyIGdlbmVzCmdlbmVzIDwtIG11dF9nZW5lcy5leHByLnBlcmNbWzJdXSRTWU1CT0wKCmRydWdzVGFibGUubXV0X2dlbmVzIDwtIGNpdmljRHJ1Z1RhYmxlKGdlbmVzLCBjaXZpY192YXJfc3VtbWFyaWVzID0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX3Zhcl9zdW1tYXJpZXMiXV0sIGNpdmljX2NsaW5fZXZpZCA9IGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV0sICBldmlkX3R5cGUgPSAiUHJlZGljdGl2ZSIsIHZhcl90eXBlID0gIm11dGF0aW9uIikKCmlmICggcGFyYW1zJGRydWdzICkgewogIGRydWdzVGFibGUubXV0X2dlbmVzW1sxXV0KfQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9ZHJ1Z3NUYWJsZS5tdXRfZ2VuZXNbWzFdXSwgZmlsZT1wYXN0ZShkcnVnc1RhYmxlRGlyLCAiZHJ1Z3NUYWJsZS5tdXRfZ2VuZXMuaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKfQpgYGAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCI8ZGV0YWlscz5cbjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT5cbjxmb250IHNpemU9XCIyXCI+IikgfWAKCmBgYHtyIGRydWdzVGFibGVfbGVnZW5kX211dF9nZW5lcywgZWNobz1GQUxTRSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMywgcmVzdWx0cz0iYXNpcyIsIGV2YWw9cGFyYW1zJGRydWdzfQpkcnVnc1RhYmxlX2xlZ2VuZCA8LSBjKCIqKltFdmlkZW5jZSBMZXZlbF0oaHR0cHM6Ly9jaXZpY2RiLm9yZy9oZWxwL2V2aWRlbmNlL2V2aWRlbmNlLWxldmVscyl7dGFyZ2V0PVwiX2JsYW5rXCJ9KioKCiogKipBIC0gVmFsaWRhdGVkIGFzc29jaWF0aW9uKio6IFByb3Zlbi9jb25zZW5zdXMgYXNzb2NpYXRpb24gaW4gaHVtYW4gbWVkaWNpbmUKKiAqKkIgLSBDbGluaWNhbCBldmlkZW5jZSoqOiBDbGluaWNhbCB0cmlhbCBvciBvdGhlciBwcmltYXJ5IHBhdGllbnQgZGF0YSBzdXBwb3J0cyBhc3NvY2lhdGlvbgoqICoqQyAtIENhc2Ugc3R1ZHkqKjogSW5kaXZpZHVhbCBjYXNlIHJlcG9ydHMgZnJvbSBjbGluaWNhbCBqb3VybmFscwoqICoqRCAtIFByZWNsaW5pY2FsIGV2aWRlbmNlKio6ICpJbiB2aXZvKiBvciAqaW4gdml0cm8qIG1vZGVscyBzdXBwb3J0IGFzc29jaWF0aW9uCiogKipFIC0gSW5mZXJlbnRpYWwgYXNzb2NpYXRpb24qKjogSW5kaXJlY3QgZXZpZGVuY2UKCioqW1RydXN0IFJhdGluZ10oaHR0cHM6Ly9jaXZpY2RiLm9yZy9oZWxwL2V2aWRlbmNlL3RydXN0LXJhdGluZ3Mpe3RhcmdldD1cIl9ibGFua1wifSoqCgoqICoqNSoqOiBTdHJvbmcsIHdlbGwgc3VwcG9ydGVkIGV2aWRlbmNlIGZyb20gYSBsYWIgb3Igam91cm5hbCB3aXRoIHJlc3BlY3RlZCBhY2FkZW1pYyBzdGFuZGluZy4gRXhwZXJpbWVudHMgYXJlIHdlbGwgY29udHJvbGxlZCwgYW5kIHJlc3VsdHMgYXJlIGNsZWFuIGFuZCByZXByb2R1Y2libGUgYWNyb3NzIG11bHRpcGxlIHJlcGxpY2F0ZXMuIEV2aWRlbmNlIGNvbmZpcm1lZCB1c2luZyBpbmRlcGVuZGVudCBtZXRob2RzLiBUaGUgc3R1ZHkgaXMgc3RhdGlzdGljYWxseSB3ZWxsIHBvd2VyZWQKKiAqKjQqKjogU3Ryb25nLCB3ZWxsIHN1cHBvcnRlZCBldmlkZW5jZS4gRXhwZXJpbWVudHMgYXJlIHdlbGwgY29udHJvbGxlZCwgYW5kIHJlc3VsdHMgYXJlIGNvbnZpbmNpbmcuIEFueSBkaXNjcmVwYW5jaWVzIGZyb20gZXhwZWN0ZWQgcmVzdWx0cyBhcmUgd2VsbC1leHBsYWluZWQgYW5kIG5vdCBjb25jZXJuaW5nCiogKiozKio6IEV2aWRlbmNlIGlzIGNvbnZpbmNpbmcsIGJ1dCBub3Qgc3VwcG9ydGVkIGJ5IGEgYnJlYWR0aCBvZiBleHBlcmltZW50cy4gTWF5IGJlIHNtYWxsZXIgc2NhbGUgcHJvamVjdHMsIG9yIG5vdmVsIHJlc3VsdHMgd2l0aG91dCBtYW55IGZvbGxvdy11cCBleHBlcmltZW50cy4gRGlzY3JlcGFuY2llcyBmcm9tIGV4cGVjdGVkIHJlc3VsdHMgYXJlIGV4cGxhaW5lZCBhbmQgbm90IGNvbmNlcm5pbmcKKiAqKjIqKjogRXZpZGVuY2UgaXMgbm90IHdlbGwgc3VwcG9ydGVkIGJ5IGV4cGVyaW1lbnRhbCBkYXRhLCBhbmQgbGl0dGxlIGZvbGxvdy11cCBkYXRhIGlzIGF2YWlsYWJsZS4gUHVibGljYXRpb24gaXMgZnJvbSBhIGpvdXJuYWwgd2l0aCBsb3cgYWNhZGVtaWMgaW1wYWN0LiBFeHBlcmltZW50cyBtYXkgbGFjayBwcm9wZXIgY29udHJvbHMsIGhhdmUgc21hbGwgc2FtcGxlIHNpemUsIG9yIGFyZSBub3Qgc3RhdGlzdGljYWxseSBjb252aW5jaW5nCiogKioxKio6IENsYWltIGlzIG5vdCBzdXBwb3J0ZWQgd2VsbCBieSBleHBlcmltZW50YWwgZXZpZGVuY2UuIFJlc3VsdHMgYXJlIG5vdCByZXByb2R1Y2libGUsIG9yIGhhdmUgdmVyeSBzbWFsbCBzYW1wbGUgc2l6ZS4gTm8gZm9sbG93LXVwIGlzIGRvbmUgdG8gdmFsaWRhdGUgbm92ZWwgY2xhaW1zCgoqKltBY3Rpb25hYmlsaXR5IFNjb3JlIF0oaHR0cHM6Ly9jaXZpY2RiLm9yZy9oZWxwL3ZhcmlhbnRzL2FjdGlvbmFiaWxpdHktc2NvcmUpe3RhcmdldD1cIl9ibGFua1wifSoqCgoqIFtDSVZpQyBBY3Rpb25hYmlsaXR5IFNjb3JlXShodHRwczovL2NpdmljZGIub3JnL2hlbHAvdmFyaWFudHMvYWN0aW9uYWJpbGl0eS1zY29yZSl7dGFyZ2V0PVwiX2JsYW5rXCJ9IGFsbG93cyB0byBhc3Nlc3MgdGhlIGFjY3VtdWxhdGlvbiBvZiBldmlkZW5jZSBmb3IgZWFjaCB2YXJpYW50LiBJdCBpcyBjYWxjdWxhdGVkIGJ5IGFkZGluZyBhbGwgRXZpZGVuY2UgSXRlbSBTY29yZXMgZm9yIGVhY2ggdmFyaWFudC4gVGhlIEV2aWRlbmNlIEl0ZW0gU2NvcmUgaXMgY2FsY3VsYXRlZCBieSBtdWx0aXBseWluZyB0aGUgZXZpZGVuY2UgbGV2ZWwgKEE9MTAgcG9pbnRzLCBCPTUgcG9pbnRzLCBDPTMgcG9pbnRzLCBEPTEgcG9pbnQsIEU9MC4yNSBwb2ludHMpIGJ5IHRoZSB0cnVzdCByYXRpbmcgKGVhY2ggU3RhciA9IDEgcG9pbnQpLiIpCgpjYXQoZHJ1Z3NUYWJsZV9sZWdlbmQpCmBgYAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIjwvZm9udD5cbjwvZGV0YWlscz4iKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIioqKiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiIyMjIC0gRnVzaW9uIGdlbmVzIC0iKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgJiYgIXJ1bkZ1c2lvbkNodW5rICkgeyBjKCJGdXNpb24gZ2VuZXMgaW5mb3JtYXRpb24gZm9yIHRoaXMgc2FtcGxlIGlzICoqTk9UIEFWQUlMQUJMRSoqLiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyAmJiBydW5GdXNpb25DaHVuayApIHsgcGFzdGUwKCI8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj4qKiIsIG5yb3coZnVzaW9uc1sgZnVzaW9ucyRnZW5lQV9kbmFfc3VwcG9ydCA9PSAiWWVzIiB8IGZ1c2lvbnMkZ2VuZUJfZG5hX3N1cHBvcnQgPT0gIlllcyIgLCBdKSwgIioqPC9zcGFuPiAqKkROQS1zdXBwb3J0ZWQqKiBmdXNpb24gZ2VuZXMgKHNlZSBbU3RydWN0dXJhbCB2YXJpYW50c10gc2VjdGlvbikgYW5kIDxzcGFuIHN0eWxlPVwiY29sb3I6IzAyZDY1M1wiPioqIiwgbnJvdyhmdXNpb25zWyBmdXNpb25zJHJlcG9ydGVkX2Z1c2lvbiA9PSAiWWVzIiAsIF0pLCAiKio8L3NwYW4+IGdlbmUgZnVzaW9ucyAqKnJlcG9ydGVkIGluIFtGdXNpb25HREJdKGh0dHBzOi8vY2NzbS51dGguZWR1L0Z1c2lvbkdEQil7dGFyZ2V0PVwiX2JsYW5rXCJ9Kiogd2VyZSBzY3JlZW5lZCBmb3Igc3VpdGFibGUgZHJ1Z3MuIikgfSBlbHNlIGlmICggcGFyYW1zJGRydWdzICYmICFydW5GdXNpb25DaHVuayApIHsgcGFzdGUwKCI8c3BhbiBzdHlsZT1cImNvbG9yOiNmZjAwMDBcIj4qKjAqKjwvc3Bhbj4gaW52b2x2aW5nICoqRE5BLXN1cHBvcnRlZCoqIGZ1c2lvbiBnZW5lcyAoc2VlIFtTdHJ1Y3R1cmFsIHZhcmlhbnRzXSBzZWN0aW9uKSBhbmQgPHNwYW4gc3R5bGU9XCJjb2xvcjojMDJkNjUzXCI+KiowKio8L3NwYW4+IGdlbmUgZnVzaW9ucyAqKnJlcG9ydGVkIGluIFtGdXNpb25HREJdKGh0dHBzOi8vY2NzbS51dGguZWR1L0Z1c2lvbkdEQil7dGFyZ2V0PVwiX2JsYW5rXCJ9Kiogd2VyZSBzY3JlZW5lZCBmb3Igc3VpdGFibGUgZHJ1Z3MuIikgfWAKCmBgYHtyIGRydWdzX3ByZWRpY3RpdmVfZnVzaW9uX2dlbmVzLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBydW5GdXNpb25DaHVua30KIyMjIyMgR2VuZXJhdGUgdGFibGUgd2l0aCBkcnVncyB0YXJnZXRpbmcgZnVzaW9uIGdlbmVzCmdlbmVzQSA8LSBhcy52ZWN0b3IoZnVzaW9uc1sgZnVzaW9uX2Fubm90JHJlcG9ydGVkX2Z1c2lvbiA9PSAiWWVzIiB8IGZ1c2lvbl9hbm5vdCRnZW5lQV9kbmFfc3VwcG9ydCA9PSAiWWVzIiB8IGZ1c2lvbl9hbm5vdCRnZW5lQl9kbmFfc3VwcG9ydCA9PSAiWWVzIiwgXSRnZW5lQSkKZ2VuZXNCIDwtIGFzLnZlY3RvcihmdXNpb25zWyBmdXNpb25fYW5ub3QkcmVwb3J0ZWRfZnVzaW9uID09ICJZZXMiIHwgZnVzaW9uX2Fubm90JGdlbmVBX2RuYV9zdXBwb3J0ID09ICJZZXMiIHwgZnVzaW9uX2Fubm90JGdlbmVCX2RuYV9zdXBwb3J0ID09ICJZZXMiLCBdJGdlbmVCKQoKZHJ1Z3NUYWJsZS5mdXNpb25fZ2VuZXMgPC0gY2l2aWNEcnVnVGFibGUoZ2VuZXMgPSB1bmlxdWUoYyhnZW5lc0EsIGdlbmVzQikpLCBjaXZpY192YXJfc3VtbWFyaWVzICA9IGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY192YXJfc3VtbWFyaWVzIl1dLCBjaXZpY19jbGluX2V2aWQgID0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX2NsaW5fZXZpZCJdXSwgIGV2aWRfdHlwZSA9ICJQcmVkaWN0aXZlIiwgdmFyX3R5cGUgPSAiZnVzaW9uIikKCmlmICggcGFyYW1zJGRydWdzICkgewogIGRydWdzVGFibGUuZnVzaW9uX2dlbmVzW1sxXV0KfQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9ZHJ1Z3NUYWJsZS5mdXNpb25fZ2VuZXNbWzFdXSwgZmlsZT1wYXN0ZShkcnVnc1RhYmxlRGlyLCAiZHJ1Z3NUYWJsZS5mdXNpb25fZ2VuZXMuaHRtbCIsIHNlcCA9ICIvIiksIHNlbGZjb250YWluZWQ9VFJVRSkKfQpgYGAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCI8ZGV0YWlscz5cbjxzdW1tYXJ5PlRhYmxlIGxlZ2VuZDwvc3VtbWFyeT5cbjxmb250IHNpemU9XCIyXCI+IikgfWAKCmBgYHtyIGRydWdzVGFibGVfbGVnZW5kX2Z1c2lvbl9nZW5lcywgZWNobz1GQUxTRSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMywgcmVzdWx0cz0iYXNpcyIsIGV2YWw9cGFyYW1zJGRydWdzfQpjYXQoZHJ1Z3NUYWJsZV9sZWdlbmQpCmBgYAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIjwvZm9udD5cbjwvZGV0YWlscz4iKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIioqKiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiIyMjIC0gU3RydWN0dXJhbCB2YXJpYW50cyAtIikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICYmICFydW5TVnNDaHVuayApIHsgYygiU1ZzIGluZm9ybWF0aW9uIGZvciB0aGlzIHNhbXBsZSBpcyAqKk5PVCBBVkFJTEFCTEUqKi4iKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgJiYgcnVuU1ZzQ2h1bmsgKSB7IHBhc3RlMCgiKioiLCBsZW5ndGgodW5pcXVlKG1hbnRhX3N2JEdlbmUpKSwgIioqIGdlbmVzIGFmZmVjdGVkIGJ5IHN0cnVjdHVyYWwgdmFyaWFudHMgKFNWcykgd2VyZSBzY3JlZW5lZCBmb3Igc3VpdGFibGUgZHJ1Z3MgKHNlZSBbU3RydWN0dXJhbCB2YXJpYW50c10gc2VjdGlvbikuIikgfSBlbHNlIGlmICggcGFyYW1zJGRydWdzICYmICFydW5TVnNDaHVuayApIHsgcGFzdGUwKCIqKjAqKiBnZW5lcyBhZmZlY3RlZCBieSBzdHJ1Y3R1cmFsIHZhcmlhbnRzIChTVnMpIHdlcmUgc2NyZWVuZWQgZm9yIHN1aXRhYmxlIGRydWdzIChzZWUgW1N0cnVjdHVyYWwgdmFyaWFudHNdIHNlY3Rpb24pLiIpIH1gCgpgYGB7ciBkcnVnc19wcmVkaWN0aXZlX3N2X2dlbmVzLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWwgPSBydW5TVnNDaHVua30KIyMjIyMgR2VuZXJhdGUgdGFibGUgd2l0aCBkcnVncyB0YXJnZXRpbmcgZHlzcmVndWxhdGVkIGNhbmNlciBnZW5lcwpnZW5lcyA8LSB1bmlxdWUobWFudGFfc3YkR2VuZSkKCmRydWdzVGFibGUuc3ZfZ2VuZXMgPC0gY2l2aWNEcnVnVGFibGUoZ2VuZXMsIGNpdmljX3Zhcl9zdW1tYXJpZXMgID0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX3Zhcl9zdW1tYXJpZXMiXV0sIGNpdmljX2NsaW5fZXZpZCAgPSBjYW5lcl9nZW5lc19hbm5vdC5saXN0W1siY2l2aWNfY2xpbl9ldmlkIl1dLCAgZXZpZF90eXBlID0gIlByZWRpY3RpdmUiLCB2YXJfdHlwZSA9IE5VTEwpCgppZiAoIHBhcmFtcyRkcnVncyApIHsKICBkcnVnc1RhYmxlLnN2X2dlbmVzW1sxXV0KfQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9ZHJ1Z3NUYWJsZS5zdl9nZW5lc1tbMV1dLCBmaWxlPXBhc3RlKGRydWdzVGFibGVEaXIsICJkcnVnc1RhYmxlLnN2X2dlbmVzLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KYGBgCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiPGRldGFpbHM+XG48c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+XG48Zm9udCBzaXplPVwiMlwiPiIpIH1gCgpgYGB7ciBkcnVnc1RhYmxlX2xlZ2VuZF9zdl9nZW5lcywgZWNobz1GQUxTRSwgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gMywgcmVzdWx0cz0iYXNpcyIsIGV2YWw9cGFyYW1zJGRydWdzfQpjYXQoZHJ1Z3NUYWJsZV9sZWdlbmQpCmBgYAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIjwvZm9udD5cbjwvZGV0YWlscz4iKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIioqKiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiIyMjIC0gQ04gYWx0ZXJlZCBnZW5lcyAtIHsudGFic2V0fSIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyAmJiAhcnVuUHVycGxlQ2h1bmsgKSB7IGMoIkNOIGluZm9ybWF0aW9uIGZvciB0aGlzIHNhbXBsZSBpcyAqKk5PVCBBVkFJTEFCTEUqKi4iKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgJiYgcnVuUHVycGxlQ2h1bmsgKSB7IHBhc3RlMCgiKioiLCBsZW5ndGgoY25fZ2VuZXMpLCAiKiogZ2VuZXMgd2l0aCBDTiB2YWx1ZXMgPj0gIiwgY25fdG9wLCAiICAoKipnYWlucyoqKSBvciA9PCAiLCBjbl9ib3R0b20sICIgKCoqbG9zc2VzKiopIHdlcmUgc2NyZWVuZWQgZm9yIHN1aXRhYmxlIGRydWdzIChzZWUgW0NOIGFsdGVyZWQgZ2VuZXNdIHNlY3Rpb24pLiIpIH0gZWxzZSBpZiAoIHBhcmFtcyRkcnVncyAmJiAhcnVuUHVycGxlQ2h1bmsgKSB7IHBhc3RlMCgiKiowKiogZ2VuZXMgd2VyZSBhZmZlY3RlZCBieSBDTiBjaGFuZ2VzIChzZWUgW0NOIGFsdGVyZWQgZ2VuZXNdIHNlY3Rpb24pLiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiIyMjIyBHYWlucyIpIH1gCgpgYGB7ciBkcnVnc19wcmVkaWN0aXZlX2NuX2FsdGVyZWRfZ2VuZXNfZ2FpbnMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blB1cnBsZUNodW5rfQojIyMjIyBHZW5lcmF0ZSB0YWJsZSB3aXRoIGRydWdzIHRhcmdldGluZyBDTiBhbHRlcmVkIGdlbmVzCmdlbmVzIDwtIGNuX2V4cHJfZ2VuZXMuZXhwci5nYWlucy5wZXJjW1syXV0kU1lNQk9MCgpkcnVnc1RhYmxlLkNOX2FsdGVyZWRfZ2VuZXNfZ2FpbnMgPC0gY2l2aWNEcnVnVGFibGUoZ2VuZXMsIGNpdmljX3Zhcl9zdW1tYXJpZXMgID0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX3Zhcl9zdW1tYXJpZXMiXV0sIGNpdmljX2NsaW5fZXZpZCAgPSBjYW5lcl9nZW5lc19hbm5vdC5saXN0W1siY2l2aWNfY2xpbl9ldmlkIl1dLCAgZXZpZF90eXBlID0gIlByZWRpY3RpdmUiLCB2YXJfdHlwZSA9ICJjb3B5X2dhaW4iKQoKaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7CiAgZHJ1Z3NUYWJsZS5DTl9hbHRlcmVkX2dlbmVzX2dhaW5zW1sxXV0KfQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9ZHJ1Z3NUYWJsZS5DTl9hbHRlcmVkX2dlbmVzX2dhaW5zW1sxXV0sIGZpbGU9cGFzdGUoZHJ1Z3NUYWJsZURpciwgImRydWdzVGFibGUuQ05fYWx0ZXJlZF9nZW5lc19nYWlucy5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CmBgYAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIjxkZXRhaWxzPlxuPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5PlxuPGZvbnQgc2l6ZT1cIjJcIj4iKSB9YAoKYGBge3IgZHJ1Z3NUYWJsZV9sZWdlbmRfY25fZ2FpbnNfZ2VuZXMsIGVjaG89RkFMU0UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIHJlc3VsdHM9ImFzaXMiLCBldmFsPXBhcmFtcyRkcnVnc30KY2F0KGRydWdzVGFibGVfbGVnZW5kKQpgYGAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCI8L2ZvbnQ+XG48L2RldGFpbHM+IikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCIqKioiKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIiMjIyMgTG9zc2VzIikgfWAKCmBgYHtyIGRydWdzX3ByZWRpY3RpdmVfY25fYWx0ZXJlZF9nZW5lc19sb3NzZXMsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbCA9IHJ1blB1cnBsZUNodW5rfQojIyMjIyBHZW5lcmF0ZSB0YWJsZSB3aXRoIGRydWdzIHRhcmdldGluZyBDTiBhbHRlcmVkIGdlbmVzCmdlbmVzIDwtIGNuX2V4cHJfZ2VuZXMuZXhwci5sb3NzZXMucGVyY1tbMl1dJFNZTUJPTAoKZHJ1Z3NUYWJsZS5DTl9hbHRlcmVkX2dlbmVzX2xvc3NlcyA8LSBjaXZpY0RydWdUYWJsZShnZW5lcywgY2l2aWNfdmFyX3N1bW1hcmllcyAgPSBjYW5lcl9nZW5lc19hbm5vdC5saXN0W1siY2l2aWNfdmFyX3N1bW1hcmllcyJdXSwgY2l2aWNfY2xpbl9ldmlkICA9IGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY19jbGluX2V2aWQiXV0sICBldmlkX3R5cGUgPSAiUHJlZGljdGl2ZSIsIHZhcl90eXBlID0gImNvcHlfbG9zcyIpCgppZiAoIHBhcmFtcyRkcnVncyApIHsKICBkcnVnc1RhYmxlLkNOX2FsdGVyZWRfZ2VuZXNfbG9zc2VzW1sxXV0KfQoKIyMjIyMgU2F2ZSB0aGUgZXhwcmVzc2lvbiB0YWJsZSBhcyBodG1sIGZpbGUKaWYgKCBwYXJhbXMkc2F2ZV90YWJsZXMgKSB7CiAgc2F2ZVdpZGdldEZpeCh3aWRnZXQ9ZHJ1Z3NUYWJsZS5DTl9hbHRlcmVkX2dlbmVzX2xvc3Nlc1tbMV1dLCBmaWxlPXBhc3RlKGRydWdzVGFibGVEaXIsICJkcnVnc1RhYmxlLkNOX2FsdGVyZWRfZ2VuZXNfbG9zc2VzLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KYGBgCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiPGRldGFpbHM+XG48c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+XG48Zm9udCBzaXplPVwiMlwiPiIpIH1gCgpgYGB7ciBkcnVnc1RhYmxlX2xlZ2VuZF9jbl9sb3NzZXNfZ2VuZXMsIGVjaG89RkFMU0UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIHJlc3VsdHM9ImFzaXMiLCBldmFsPXBhcmFtcyRkcnVnc30KY2F0KGRydWdzVGFibGVfbGVnZW5kKQpgYGAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCI8L2ZvbnQ+XG48L2RldGFpbHM+IikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCIqKioiKSB9YAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIiMjIyAtIEhSRCBnZW5lcyAtIikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBwYXN0ZTAoIioqIiwgbGVuZ3RoKHdoaWNoKGhyZF9nZW5lcy5leHByLnBlcmNbWzJdXSRTWU1CT0wgJWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkpKSwgIioqIHJlbGlhYmx5IG1lYXN1cmVkIFtIUkQgZ2VuZXNdIHdlcmUgc2NyZWVuZWQgZm9yIHN1aXRhYmxlIGRydWdzIChzZWUgW0hSRCBnZW5lc10gc2VjdGlvbikuIikgfWAKCmBgYHtyIGRydWdzX3ByZWRpY3RpdmVfaHJkX2dlbmVzLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIEdlbmVyYXRlIHRhYmxlIHdpdGggZHJ1Z3MgdGFyZ2V0aW5nIG11dGF0ZWQgY2FuY2VyIGdlbmVzCmdlbmVzIDwtIGhyZF9nZW5lcy5leHByLnBlcmNbWzJdXSRTWU1CT0xbIGhyZF9nZW5lcy5leHByLnBlcmNbWzJdXSRTWU1CT0wgJWluJSByb3duYW1lcyhyZWZfZGF0YXNldC5saXN0W1tkYXRhc2V0XV1bWyJkYXRhX3RvX3JlcG9ydCJdXSkgXQoKZHJ1Z3NUYWJsZS5ocmRfZ2VuZXMgPC0gY2l2aWNEcnVnVGFibGUoZ2VuZXMsIGNpdmljX3Zhcl9zdW1tYXJpZXMgPSBjYW5lcl9nZW5lc19hbm5vdC5saXN0W1siY2l2aWNfdmFyX3N1bW1hcmllcyJdXSwgY2l2aWNfY2xpbl9ldmlkID0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX2NsaW5fZXZpZCJdXSwgIGV2aWRfdHlwZSA9ICJQcmVkaWN0aXZlIiwgdmFyX3R5cGUgPSAibXV0YXRpb24iKQoKaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7CiAgZHJ1Z3NUYWJsZS5ocmRfZ2VuZXNbWzFdXQp9CgojIyMjIyBTYXZlIHRoZSBleHByZXNzaW9uIHRhYmxlIGFzIGh0bWwgZmlsZQppZiAoIHBhcmFtcyRzYXZlX3RhYmxlcyApIHsKICBzYXZlV2lkZ2V0Rml4KHdpZGdldD1kcnVnc1RhYmxlLmhyZF9nZW5lc1tbMV1dLCBmaWxlPXBhc3RlKGRydWdzVGFibGVEaXIsICJkcnVnc1RhYmxlLmhyZF9nZW5lcy5odG1sIiwgc2VwID0gIi8iKSwgc2VsZmNvbnRhaW5lZD1UUlVFKQp9CmBgYAoKYHIgaWYgKCBwYXJhbXMkZHJ1Z3MgKSB7IGMoIjxkZXRhaWxzPlxuPHN1bW1hcnk+VGFibGUgbGVnZW5kPC9zdW1tYXJ5PlxuPGZvbnQgc2l6ZT1cIjJcIj4iKSB9YAoKYGBge3IgZHJ1Z3NUYWJsZV9sZWdlbmRfaHJkX2dlbmVzLCBlY2hvPUZBTFNFLCBjb21tZW50ID0gTkEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSAzLCByZXN1bHRzPSJhc2lzIiwgZXZhbD1wYXJhbXMkZHJ1Z3N9CmNhdChkcnVnc1RhYmxlX2xlZ2VuZCkKYGBgCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiPC9mb250PlxuPC9kZXRhaWxzPiIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiKioqIikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCIjIyMgLSBDYW5jZXIgZ2VuZXMgLSIpIH1gCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgcGFzdGUwKCIqKjUwKiogY2FuY2VyIGdlbmVzIHdpdGggdGhlIGdyZWF0ZXN0IGRpZmZlcmVuY2UgaW4gc3RhbmRhcmlzZWQgKFotc2NvcmUpIG1STkEgZXhwcmVzc2lvbiB2YWx1ZXMgYmV0d2VlbiBwYXRpZW50J3Mgc2FtcGxlIGFuZCB0aGUgYXZlcmFnZSBtUk5BIGV4cHJlc3Npb24gaW4gc2FtcGxlcyBmcm9tIGNhbmNlciBwYXRpZW50cyB3ZXJlIHNjcmVlbmVkIGZvciBzdWl0YWJsZSBkcnVncyAoc2VlIFtDYW5jZXIgZ2VuZXNdIHNlY3Rpb24pLiIpIH1gCgpgYGB7ciBkcnVnc19wcmVkaWN0aXZlX2NhbmNlcl9nZW5lcywgY29tbWVudCA9IE5BLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIyMjIyBVcGRhdGUgTXlTUUwgY29tbWVuZCB0byBwb3B1bGF0ZSBSTkEtc2VxIGRhdGEgcG9ydGFsCm15c3FsX3BvcHVsYXRlIDwtIHBhc3RlMChteXNxbF9wb3B1bGF0ZSwgIixEcnVnIG1hdGNoaW5nIikKbXlzcWxfcG9wdWxhdGVfdXBkYXRlIDwtIHBhc3RlMChteXNxbF9wb3B1bGF0ZV91cGRhdGUsICIsRHJ1ZyBtYXRjaGluZyIpCgojIyMjIyBHZW5lcmF0ZSB0YWJsZSB3aXRoIGRydWdzIHRhcmdldGluZyBkeXNyZWd1bGF0ZWQgY2FuY2VyIGdlbmVzCmdlbmVzIDwtIGNhbmNlcl9nZW5lcy5leHByLnBlcmNbWzJdXSRTWU1CT0xbMTo1MF0KCmRydWdzVGFibGUuY2FuY2VyX2dlbmVzIDwtIGNpdmljRHJ1Z1RhYmxlKGdlbmVzLCBjaXZpY192YXJfc3VtbWFyaWVzICA9IGNhbmVyX2dlbmVzX2Fubm90Lmxpc3RbWyJjaXZpY192YXJfc3VtbWFyaWVzIl1dLCBjaXZpY19jbGluX2V2aWQgID0gY2FuZXJfZ2VuZXNfYW5ub3QubGlzdFtbImNpdmljX2NsaW5fZXZpZCJdXSwgIGV2aWRfdHlwZSA9ICJQcmVkaWN0aXZlIiwgdmFyX3R5cGUgPSAiZXhwcmVzc2lvbiIpCgppZiAoIHBhcmFtcyRkcnVncyApIHsKICBkcnVnc1RhYmxlLmNhbmNlcl9nZW5lc1tbMV1dCn0KCiMjIyMjIFNhdmUgdGhlIGV4cHJlc3Npb24gdGFibGUgYXMgaHRtbCBmaWxlCmlmICggcGFyYW1zJHNhdmVfdGFibGVzICkgewogIHNhdmVXaWRnZXRGaXgod2lkZ2V0PWRydWdzVGFibGUuY2FuY2VyX2dlbmVzW1sxXV0sIGZpbGU9cGFzdGUoZHJ1Z3NUYWJsZURpciwgImRydWdzVGFibGUuY2FuY2VyX2dlbmVzLmh0bWwiLCBzZXAgPSAiLyIpLCBzZWxmY29udGFpbmVkPVRSVUUpCn0KYGBgCgpgciBpZiAoIHBhcmFtcyRkcnVncyApIHsgYygiPGRldGFpbHM+XG48c3VtbWFyeT5UYWJsZSBsZWdlbmQ8L3N1bW1hcnk+XG48Zm9udCBzaXplPVwiMlwiPiIpIH1gCgpgYGB7ciBkcnVnc1RhYmxlX2xlZ2VuZF9jYW5jZXJfZ2VuZXMsIGVjaG89RkFMU0UsIGNvbW1lbnQgPSBOQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDMsIHJlc3VsdHM9ImFzaXMiLCBldmFsPXBhcmFtcyRkcnVnc30KY2F0KGRydWdzVGFibGVfbGVnZW5kKQpgYGAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCI8L2ZvbnQ+XG48L2RldGFpbHM+IikgfWAKCmByIGlmICggcGFyYW1zJGRydWdzICkgeyBjKCIqKioiKSB9YAoKYGBge3IgbXlzcWxfcG9wdWxhdGVfZmluYWxpc2UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIyMjIEZpbmFsaXNlIGFuZCB3cml0ZSBpbnRvIGEgZmlsZSB0aGUgTXlTUUwgY29tbWVuZCB0byBwb3B1bGF0ZSBSTkEtc2VxIGRhdGEgcG9ydGFsCiMjIyMjIEFkZCBpbnB1dCBkYXRhIGluZm8KbXlzcWxfcG9wdWxhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlLCAiLElucHV0IGRhdGEiKQpteXNxbF9wb3B1bGF0ZV91cGRhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlX3VwZGF0ZSwgIixJbnB1dCBkYXRhIikKCiMjIyMjIEFkZCBjbGluaWNhbCBkYXRhIGlmIGF2YWlsYWJsZQppZiAocnVuQ2xpbmljYWxDaHVuayApIHsKICBteXNxbF9wb3B1bGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGUsICIsQ2xpbmljYWwgaW5mb3JtYXRpb24iKQogIG15c3FsX3BvcHVsYXRlX3VwZGF0ZSA8LSBwYXN0ZTAobXlzcWxfcG9wdWxhdGVfdXBkYXRlLCAiLENsaW5pY2FsIGluZm9ybWF0aW9uIikKfQoKbXlzcWxfcG9wdWxhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlLCAiXCIsIFwiVHJhbnNjcmlwdG9tZSBzdW1tYXJ5IGZvciBzYW1wbGUgIiwgc2FtcGxlX25hbWUsICIgZ2VuZXJhdGVkIG9uICIsIFN5cy5EYXRlKCksICJcIiIsICIsIFwiIiwgU3lzLkRhdGUoKSwgIlwiICkiKQpteXNxbF9wb3B1bGF0ZV91cGRhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlX3VwZGF0ZSwgIlwiLCBTdW1tYXJ5PVwiVHJhbnNjcmlwdG9tZSBzdW1tYXJ5IGZvciBzYW1wbGUgIiwgc2FtcGxlX25hbWUsICIgZ2VuZXJhdGVkIG9uICIsIFN5cy5EYXRlKCksICJcIiIsICIsIERhdGU9XCIiLCBTeXMuRGF0ZSgpLCAiXCI7IikKbXlzcWxfcG9wdWxhdGUgPC0gcGFzdGUwKG15c3FsX3BvcHVsYXRlLCAiXG4gICIsIG15c3FsX3BvcHVsYXRlX3VwZGF0ZSwgIlxuU0VUIEBJRCA6PSAwO1xuVVBEQVRFIFJOQXNlcV9yZXBvcnRzIFNFVCBJRCA9ICggU0VMRUNUIEBJRCA6PSBASUQgKyAxICk7IikKd3JpdGVMaW5lcyhteXNxbF9wb3B1bGF0ZSwgY29uID0gcGFzdGUwKHJlc3VsdHNfZGlyLCAiLyIsIHNhbXBsZV9uYW1lLCAiLlJOQXNlcV9yZXBvcnQuc3FsIikpCmBgYAoKIyMgQWRkZW5kdW0KCjxkZXRhaWxzPgo8c3VtbWFyeT5QYXJhbWV0ZXJzPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KCmBgYHtyIHBhcmFtc19pbmZvLCBjb21tZW50ID0gTkF9CmZvciAoIGkgaW4gMTpsZW5ndGgocGFyYW1zKSApIHsKICBjYXQocGFzdGUoIlBhcmFtZXRlcjogIiwgbmFtZXMocGFyYW1zKVtpXSwgIlxuVmFsdWU6ICIsIHBhc3RlKHVubGlzdChwYXJhbXNbaV0pLCBjb2xsYXBzZSA9ICIsIiksICJcblxuIiwgc2VwPSIiKSkKfQpgYGAKCjwvZm9udD4KPC9kZXRhaWxzPgoKPGRldGFpbHM+CjxzdW1tYXJ5PlJlcG9ydGVyIGRldGFpbHM8L3N1bW1hcnk+Cjxmb250IHNpemU9IjIiPgoKYGBge3IgcmVwb3J0ZXJfZGV0YWlscywgY29tbWVudCA9IE5BfQpjYXQocGFzdGUwKCJUaGUgcmVwb3J0IHdhcyBnZW5lcmF0ZWQgYnkgXCIiLCBTeXMuaW5mbygpWyAidXNlciJdLCAiXCIgdXNpbmcgXCIiLCAgU3lzLmluZm8oKVsgIm5vZGVuYW1lIl0sICJcIiBub2RlIGFuZCBcIiIsICBTeXMuaW5mbygpWyAic3lzbmFtZSJdLCAiXCIgb3BlcmF0aW5nIHN5c3RlbS4iKSkKYGBgCgo8L2ZvbnQ+CjwvZGV0YWlscz4KCjxkZXRhaWxzPgo8c3VtbWFyeT5TZXNzaW9uIGluZm9ybWF0aW9uPC9zdW1tYXJ5Pgo8Zm9udCBzaXplPSIyIj4KCmBgYHtyIHNlc3Npb25faW5mbywgY29tbWVudCA9IE5BfQpkZXZ0b29sczo6c2Vzc2lvbl9pbmZvKCkKYGBgCgo8L2ZvbnQ+CjwvZGV0YWlscz4K